Browse Source

[core][config] Hide config values having secret attribute as True (#3841)

Harsh Gupta 1 year ago
parent
commit
ad12816d88
2 changed files with 62 additions and 48 deletions
  1. 1 1
      desktop/core/src/desktop/api2.py
  2. 61 47
      desktop/core/src/desktop/lib/conf.py

+ 1 - 1
desktop/core/src/desktop/api2.py

@@ -177,7 +177,7 @@ def get_hue_config(request):
         conf['values'] = recurse_conf(module.get().values())
         conf['values'] = recurse_conf(module.get().values())
       else:
       else:
         conf['default'] = str(module.config.default)
         conf['default'] = str(module.config.default)
-        if 'password' in module.config.key:
+        if module.config.secret or 'password' in module.config.key:
           conf['value'] = '*' * 10
           conf['value'] = '*' * 10
         elif sys.version_info[0] > 2:
         elif sys.version_info[0] > 2:
           conf['value'] = str(module.get_raw())
           conf['value'] = str(module.get_raw())

+ 61 - 47
desktop/core/src/desktop/lib/conf.py

@@ -64,38 +64,34 @@ variables.
 # The Config object unfortunately has a kwarg called "type", and everybody is
 # The Config object unfortunately has a kwarg called "type", and everybody is
 # using it. So instead of breaking compatibility, we make a "pytype" alias.
 # using it. So instead of breaking compatibility, we make a "pytype" alias.
 
 
-from __future__ import print_function
-from six import string_types
-from builtins import object
-pytype = type
-
+import os
+import re
+import sys
 import json
 import json
 import logging
 import logging
 import numbers
 import numbers
-import os
 import textwrap
 import textwrap
-import re
 import subprocess
 import subprocess
-import sys
+from builtins import object
 
 
-from django.utils.encoding import smart_str
 from configobj import ConfigObj, ConfigObjError
 from configobj import ConfigObj, ConfigObjError
+from django.utils.encoding import smart_str
+from django.utils.translation import gettext_lazy as _t
+from six import string_types
 
 
-from desktop.lib.paths import get_desktop_root, get_build_dir
+from desktop.lib.paths import get_build_dir, get_desktop_root
 
 
 try:
 try:
   from collections import OrderedDict
   from collections import OrderedDict
 except ImportError:
 except ImportError:
-  from ordereddict import OrderedDict # Python 2.6
+  from ordereddict import OrderedDict  # Python 2.6
+
 
 
-if sys.version_info[0] > 2:
-  from django.utils.translation import gettext_lazy as _t
-else:
-  from django.utils.translation import ugettext_lazy as _t
+pytype = type
 
 
 
 
 # Magical object for use as a "symbol"
 # Magical object for use as a "symbol"
-_ANONYMOUS = ("_ANONYMOUS")
+_ANONYMOUS = "_ANONYMOUS"
 
 
 # Supported thrift transports
 # Supported thrift transports
 SUPPORTED_THRIFT_TRANSPORTS = ('buffered', 'framed')
 SUPPORTED_THRIFT_TRANSPORTS = ('buffered', 'framed')
@@ -107,6 +103,7 @@ LOG = logging.getLogger()
 
 
 __all__ = ["UnspecifiedConfigSection", "ConfigSection", "Config", "load_confs", "coerce_bool", "coerce_csv", "coerce_json_dict"]
 __all__ = ["UnspecifiedConfigSection", "ConfigSection", "Config", "load_confs", "coerce_bool", "coerce_csv", "coerce_json_dict"]
 
 
+
 class BoundConfig(object):
 class BoundConfig(object):
   def __init__(self, config, bind_to, grab_key=_ANONYMOUS, prefix=''):
   def __init__(self, config, bind_to, grab_key=_ANONYMOUS, prefix=''):
     """
     """
@@ -177,16 +174,18 @@ class BoundConfig(object):
     Note that self is a new object at every access,
     Note that self is a new object at every access,
     but self.bind_to is shared, so we can modify that.
     but self.bind_to is shared, so we can modify that.
     """
     """
+
     def set_data_presence(data, presence):
     def set_data_presence(data, presence):
       self.bind_to[self.grab_key] = data
       self.bind_to[self.grab_key] = data
       if not presence:
       if not presence:
         del self.bind_to[self.grab_key]
         del self.bind_to[self.grab_key]
-    assert self.grab_key is not _ANONYMOUS # TODO(todd) really?
+
+    assert self.grab_key is not _ANONYMOUS  # TODO(todd) really?
     old_data = self.bind_to.get(self.grab_key)
     old_data = self.bind_to.get(self.grab_key)
     old_presence = self.grab_key in self.bind_to
     old_presence = self.grab_key in self.bind_to
 
 
     set_data_presence(data, present)
     set_data_presence(data, present)
-    return (lambda: set_data_presence(old_data, old_presence))
+    return lambda: set_data_presence(old_data, old_presence)
 
 
   def validate(self):
   def validate(self):
     self.config.validate(self.bind_to)
     self.config.validate(self.bind_to)
@@ -195,12 +194,11 @@ class BoundConfig(object):
     self.config.print_help(*args, **kwargs)
     self.config.print_help(*args, **kwargs)
 
 
   def __repr__(self):
   def __repr__(self):
-    return repr("%s(config=%s, bind_to=%s, grab_key=%s)" % (str(self.__class__),  repr(self.config), repr(self.bind_to), repr(self.grab_key)))
+    return f"{self.__class__.__name__}(config={self.config!r}, bind_to={self.bind_to!r}, grab_key={self.grab_key!r})"
 
 
 
 
 class Config(object):
 class Config(object):
-  def __init__(self, key=_ANONYMOUS, default=None, dynamic_default=None,
-               required=False, help=None, type=str, private=False):
+  def __init__(self, key=_ANONYMOUS, default=None, dynamic_default=None, required=False, help=None, type=str, private=False, secret=False):
     """
     """
     Initialize a new Configurable variable.
     Initialize a new Configurable variable.
 
 
@@ -213,6 +211,7 @@ class Config(object):
                     str is the default. Should raise an exception in the case
                     str is the default. Should raise an exception in the case
                     that it cannot be coerced.
                     that it cannot be coerced.
     @param private  if True, does not emit help text
     @param private  if True, does not emit help text
+    @param secret   if True, masks the config value
     """
     """
     if not callable(type):
     if not callable(type):
       raise ValueError("%s: The type argument '%s()' is not callable" % (key, type))
       raise ValueError("%s: The type argument '%s()' is not callable" % (key, type))
@@ -223,14 +222,11 @@ class Config(object):
     if dynamic_default is not None and not dynamic_default.__doc__ and not private:
     if dynamic_default is not None and not dynamic_default.__doc__ and not private:
       raise ValueError("Dynamic default '%s' must have __doc__ defined!" % (key,))
       raise ValueError("Dynamic default '%s' must have __doc__ defined!" % (key,))
 
 
-    if (isinstance(default, numbers.Number) or pytype(default) is bool) and \
-          not isinstance(type(default), pytype(default)):
-      raise ValueError("%s: '%s' does not match that of the default value %r (%s)"
-                      % (key, type, default, pytype(default)))
+    if (isinstance(default, numbers.Number) or pytype(default) is bool) and not isinstance(type(default), pytype(default)):
+      raise ValueError("%s: '%s' does not match that of the default value %r (%s)" % (key, type, default, pytype(default)))
 
 
-    if type == bool:
-      LOG.warning("%s is of type bool. Resetting it as type 'coerce_bool'."
-               " Please fix it permanently" % (key,))
+    if type is bool:
+      LOG.warning("%s is of type bool. Resetting it as type 'coerce_bool'." " Please fix it permanently" % (key,))
       type = coerce_bool
       type = coerce_bool
 
 
     self.key = key
     self.key = key
@@ -240,11 +236,11 @@ class Config(object):
     self.help = help
     self.help = help
     self.type = type
     self.type = type
     self.private = private
     self.private = private
+    self.secret = secret
 
 
     # It makes no sense to be required if you have a default,
     # It makes no sense to be required if you have a default,
     # since you'll never throw the "not set" error.
     # since you'll never throw the "not set" error.
-    assert not (self.required and self.default), \
-           "Config cannot be required if it has a default."
+    assert not (self.required and self.default), "Config cannot be required if it has a default."
 
 
   def bind(self, conf, prefix):
   def bind(self, conf, prefix):
     """Rather than doing the lookup now and assigning self.value or something,
     """Rather than doing the lookup now and assigning self.value or something,
@@ -323,14 +319,12 @@ class Config(object):
   def get_presentable_help_text(self, indent=0):
   def get_presentable_help_text(self, indent=0):
     indent_str = " " * indent
     indent_str = " " * indent
     help = self.help or "[no help text provided]"
     help = self.help or "[no help text provided]"
-    help = textwrap.fill(help,
-                         initial_indent=(indent_str + "  "),
-                         subsequent_indent=(indent_str + "    "))
+    help = textwrap.fill(help, initial_indent=(indent_str + "  "), subsequent_indent=(indent_str + "    "))
     return help
     return help
 
 
   def get_presentable_key(self):
   def get_presentable_key(self):
     if self.key is _ANONYMOUS:
     if self.key is _ANONYMOUS:
-      return "<user specified name>" # TODO(todd) add "metavar" like optparse
+      return "<user specified name>"  # TODO(todd) add "metavar" like optparse
     else:
     else:
       return self.key
       return self.key
 
 
@@ -340,6 +334,7 @@ class Config(object):
       return self.dynamic_default()
       return self.dynamic_default()
     return self.default_value
     return self.default_value
 
 
+
 class BoundContainer(BoundConfig):
 class BoundContainer(BoundConfig):
   """Binds a ConfigSection to actual data."""
   """Binds a ConfigSection to actual data."""
 
 
@@ -363,6 +358,7 @@ class BoundContainer(BoundConfig):
   def keys(self):
   def keys(self):
     return list(self.get_data_dict().keys())
     return list(self.get_data_dict().keys())
 
 
+
 class BoundContainerWithGetAttr(BoundContainer):
 class BoundContainerWithGetAttr(BoundContainer):
   """
   """
   A configuration bound to a data container where we expect
   A configuration bound to a data container where we expect
@@ -371,9 +367,11 @@ class BoundContainerWithGetAttr(BoundContainer):
 
 
   This is used by ConfigSection
   This is used by ConfigSection
   """
   """
+
   def __getattr__(self, attr):
   def __getattr__(self, attr):
     return self.config.get_member(self.get_data_dict(), attr, self.prefix)
     return self.config.get_member(self.get_data_dict(), attr, self.prefix)
 
 
+
 class BoundContainerWithGetItem(BoundContainer):
 class BoundContainerWithGetItem(BoundContainer):
   """
   """
   A configuration bound to a data container where we expect
   A configuration bound to a data container where we expect
@@ -381,6 +379,7 @@ class BoundContainerWithGetItem(BoundContainer):
 
 
   This is used for UnspecifiedConfigSection
   This is used for UnspecifiedConfigSection
   """
   """
+
   def __getitem__(self, attr):
   def __getitem__(self, attr):
     if attr in self.__dict__:
     if attr in self.__dict__:
       return self.__dict__[attr]
       return self.__dict__[attr]
@@ -393,6 +392,7 @@ class ConfigSection(Config):
   a priori. For example, this can be used to group configuration
   a priori. For example, this can be used to group configuration
   for a cluster.
   for a cluster.
   """
   """
+
   def __init__(self, key=_ANONYMOUS, members=None, **kwargs):
   def __init__(self, key=_ANONYMOUS, members=None, **kwargs):
     """Initializes a ConfigSection
     """Initializes a ConfigSection
 
 
@@ -407,7 +407,6 @@ class ConfigSection(Config):
     for member in members.values():
     for member in members.values():
       assert member.key is not _ANONYMOUS
       assert member.key is not _ANONYMOUS
 
 
-
   def update_members(self, new_members, overwrite=True):
   def update_members(self, new_members, overwrite=True):
     """
     """
     Add the new_members to this ConfigSection.
     Add the new_members to this ConfigSection.
@@ -424,7 +423,6 @@ class ConfigSection(Config):
           del new_members[k]
           del new_members[k]
     self.members.update(new_members)
     self.members.update(new_members)
 
 
-
   def bind(self, config, prefix):
   def bind(self, config, prefix):
     return BoundContainerWithGetAttr(self, bind_to=config, grab_key=self.key, prefix=prefix)
     return BoundContainerWithGetAttr(self, bind_to=config, grab_key=self.key, prefix=prefix)
 
 
@@ -435,8 +433,7 @@ class ConfigSection(Config):
     The keys are those specified in the members dict, and the values
     The keys are those specified in the members dict, and the values
     are bound configuration parameters.
     are bound configuration parameters.
     """
     """
-    return dict([(key, self.get_member(raw, key, prefix))
-                 for key in self.members.keys()])
+    return dict([(key, self.get_member(raw, key, prefix)) for key in self.members.keys()])
 
 
   def get_member(self, data, attr, prefix):
   def get_member(self, data, attr, prefix):
     if self.key is not _ANONYMOUS:
     if self.key is not _ANONYMOUS:
@@ -458,6 +455,7 @@ class ConfigSection(Config):
     for programmer_key, config in sorted(iter(self.members.items()), key=lambda x: x[1].key):
     for programmer_key, config in sorted(iter(self.members.items()), key=lambda x: x[1].key):
       config.print_help(out=out, indent=new_indent)
       config.print_help(out=out, indent=new_indent)
 
 
+
 class UnspecifiedConfigSection(Config):
 class UnspecifiedConfigSection(Config):
   """
   """
   A special Config that maps a section name to a list of anonymous subsections.
   A special Config that maps a section name to a list of anonymous subsections.
@@ -473,10 +471,11 @@ class UnspecifiedConfigSection(Config):
   (2) an anonymous grab_key, and
   (2) an anonymous grab_key, and
   (3) a `prefix' containing the prefix plus the actual key name.
   (3) a `prefix' containing the prefix plus the actual key name.
   """
   """
+
   def __init__(self, key=_ANONYMOUS, each=None, **kwargs):
   def __init__(self, key=_ANONYMOUS, each=None, **kwargs):
     super(UnspecifiedConfigSection, self).__init__(key, default={}, **kwargs)
     super(UnspecifiedConfigSection, self).__init__(key, default={}, **kwargs)
     assert each.key is _ANONYMOUS
     assert each.key is _ANONYMOUS
-    self.each = each    # `each' is a ConfigSection
+    self.each = each  # `each' is a ConfigSection
 
 
   def bind(self, config, prefix):
   def bind(self, config, prefix):
     return BoundContainerWithGetItem(self, bind_to=config, grab_key=self.key, prefix=prefix)
     return BoundContainerWithGetItem(self, bind_to=config, grab_key=self.key, prefix=prefix)
@@ -487,8 +486,7 @@ class UnspecifiedConfigSection(Config):
 
 
     The keys are the keys specified by the user in the config file.
     The keys are the keys specified by the user in the config file.
     """
     """
-    return OrderedDict([(key, self.get_member(raw, key, prefix))
-                 for key in raw.keys()])
+    return OrderedDict([(key, self.get_member(raw, key, prefix)) for key in raw.keys()])
 
 
   def get_member(self, data, attr, prefix=''):
   def get_member(self, data, attr, prefix=''):
     tail = self.key + '.' + attr
     tail = self.key + '.' + attr
@@ -503,7 +501,8 @@ class UnspecifiedConfigSection(Config):
     print(self.get_presentable_help_text(indent=indent), file=out)
     print(self.get_presentable_help_text(indent=indent), file=out)
     print(file=out)
     print(file=out)
     print(indent_str + "  Consists of some number of sections like:", file=out)
     print(indent_str + "  Consists of some number of sections like:", file=out)
-    self.each.print_help(out=out, indent=indent+2)
+    self.each.print_help(out=out, indent=indent + 2)
+
 
 
 def _configs_from_dir(conf_dir):
 def _configs_from_dir(conf_dir):
   """
   """
@@ -522,6 +521,7 @@ def _configs_from_dir(conf_dir):
     conf['DEFAULT'] = dict(desktop_root=get_desktop_root(), build_dir=get_build_dir())
     conf['DEFAULT'] = dict(desktop_root=get_desktop_root(), build_dir=get_build_dir())
     yield conf
     yield conf
 
 
+
 def load_confs(conf_source=None):
 def load_confs(conf_source=None):
   """Loads and merges all of the configurations passed in,
   """Loads and merges all of the configurations passed in,
   returning a ConfigObj for the result.
   returning a ConfigObj for the result.
@@ -538,6 +538,7 @@ def load_confs(conf_source=None):
     conf.merge(in_conf)
     conf.merge(in_conf)
   return conf
   return conf
 
 
+
 def _bind_module_members(module, data, section):
 def _bind_module_members(module, data, section):
   """
   """
   Bind all Config instances found inside the given module
   Bind all Config instances found inside the given module
@@ -580,7 +581,7 @@ def bind_module_config(mod, conf_data, config_key):
   if hasattr(mod, "CONFIGURATION_SECTION"):
   if hasattr(mod, "CONFIGURATION_SECTION"):
     section = mod.CONFIGURATION_SECTION
     section = mod.CONFIGURATION_SECTION
   elif mod.__name__.endswith(".conf"):
   elif mod.__name__.endswith(".conf"):
-    section = mod.__name__[:-len(".conf")]
+    section = mod.__name__[: -len(".conf")]
   else:
   else:
     section = mod.__name__
     section = mod.__name__
 
 
@@ -593,6 +594,7 @@ def bind_module_config(mod, conf_data, config_key):
   members = _bind_module_members(mod, bind_data, section)
   members = _bind_module_members(mod, bind_data, section)
   return ConfigSection(section, members=members, help=mod.__doc__)
   return ConfigSection(section, members=members, help=mod.__doc__)
 
 
+
 def initialize(modules, config_dir):
 def initialize(modules, config_dir):
   """
   """
   Set up the GLOBAL_CONFIG variable by loading all configuration
   Set up the GLOBAL_CONFIG variable by loading all configuration
@@ -618,6 +620,7 @@ def initialize(modules, config_dir):
     GLOBAL_CONFIG = new_config.bind(conf_data, prefix='')
     GLOBAL_CONFIG = new_config.bind(conf_data, prefix='')
   return
   return
 
 
+
 def is_anonymous(key):
 def is_anonymous(key):
   return key == _ANONYMOUS
   return key == _ANONYMOUS
 
 
@@ -641,12 +644,14 @@ def coerce_bool(value):
     return True
     return True
   raise Exception("Could not coerce %r to boolean value" % (value,))
   raise Exception("Could not coerce %r to boolean value" % (value,))
 
 
+
 def coerce_string(value):
 def coerce_string(value):
-  if type(value) == list:
+  if type(value) is list:
     return ','.join(value)
     return ','.join(value)
   else:
   else:
     return value
     return value
 
 
+
 def coerce_csv(value):
 def coerce_csv(value):
   if isinstance(value, str):
   if isinstance(value, str):
     return value.split(',')
     return value.split(',')
@@ -654,6 +659,7 @@ def coerce_csv(value):
     return value
     return value
   raise Exception("Could not coerce %r to csv array." % value)
   raise Exception("Could not coerce %r to csv array." % value)
 
 
+
 def coerce_json_dict(value):
 def coerce_json_dict(value):
   if isinstance(value, string_types):
   if isinstance(value, string_types):
     return json.loads(value)
     return json.loads(value)
@@ -661,14 +667,17 @@ def coerce_json_dict(value):
     return value
     return value
   raise Exception("Could not coerce %r to json dictionary." % value)
   raise Exception("Could not coerce %r to json dictionary." % value)
 
 
+
 def list_of_compiled_res(skip_empty=False):
 def list_of_compiled_res(skip_empty=False):
   def fn(list_of_strings):
   def fn(list_of_strings):
     if isinstance(list_of_strings, string_types):
     if isinstance(list_of_strings, string_types):
       list_of_strings = list_of_strings.split(',')
       list_of_strings = list_of_strings.split(',')
     list_of_strings = [string if skip_empty else True for string in list_of_strings]
     list_of_strings = [string if skip_empty else True for string in list_of_strings]
     return list(re.compile(x) for x in list_of_strings)
     return list(re.compile(x) for x in list_of_strings)
+
   return fn
   return fn
 
 
+
 def validate_path(confvar, is_dir=None, fs=os.path, message='Path does not exist on the filesystem.'):
 def validate_path(confvar, is_dir=None, fs=os.path, message='Path does not exist on the filesystem.'):
   """
   """
   Validate that the value of confvar is an existent path.
   Validate that the value of confvar is an existent path.
@@ -687,7 +696,8 @@ def validate_path(confvar, is_dir=None, fs=os.path, message='Path does not exist
         return [(confvar, 'Not a directory.')]
         return [(confvar, 'Not a directory.')]
     elif not fs.isfile(path):
     elif not fs.isfile(path):
       return [(confvar, 'Not a file.')]
       return [(confvar, 'Not a file.')]
-  return [ ]
+  return []
+
 
 
 def validate_port(confvar):
 def validate_port(confvar):
   """
   """
@@ -702,7 +712,8 @@ def validate_port(confvar):
       return error_res
       return error_res
   except ValueError:
   except ValueError:
     return error_res
     return error_res
-  return [ ]
+  return []
+
 
 
 def validate_thrift_transport(confvar):
 def validate_thrift_transport(confvar):
   """
   """
@@ -710,13 +721,16 @@ def validate_thrift_transport(confvar):
   Returns [(confvar, error_msg)] or []
   Returns [(confvar, error_msg)] or []
   """
   """
   transport = confvar.get()
   transport = confvar.get()
-  error_res = [(confvar, 'Thrift transport %s not supported. Please choose a supported transport: %s' % (transport, ', '.join(SUPPORTED_THRIFT_TRANSPORTS)))]
+  error_res = [
+    (confvar, f"Thrift transport {transport} not supported. Please choose a supported transport: {', '.join(SUPPORTED_THRIFT_TRANSPORTS)}")
+  ]
 
 
   if transport not in SUPPORTED_THRIFT_TRANSPORTS:
   if transport not in SUPPORTED_THRIFT_TRANSPORTS:
     return error_res
     return error_res
 
 
   return []
   return []
 
 
+
 def coerce_password_from_script(script):
 def coerce_password_from_script(script):
   p = subprocess.Popen(script, shell=True, stdout=subprocess.PIPE)
   p = subprocess.Popen(script, shell=True, stdout=subprocess.PIPE)
   stdout, stderr = p.communicate()
   stdout, stderr = p.communicate()