mirror of
https://github.com/beetbox/beets.git
synced 2026-01-03 22:42:44 +01:00
Config: redact sensitive fields with -r option
- `config -r` will show 'REDACTED' instead of the actual value - Has a default list of field names that should be redacted (e.g., 'password') - Plugins can add redacted fields that they introduce
This commit is contained in:
parent
c4d7dd0d6d
commit
778138f2cd
2 changed files with 29 additions and 2 deletions
|
|
@ -1475,7 +1475,7 @@ def config_func(lib, opts, args):
|
|||
|
||||
# Dump configuration.
|
||||
else:
|
||||
print(config.dump(full=opts.defaults))
|
||||
print(config.dump(full=opts.defaults, redact_fields=opts.redacted))
|
||||
|
||||
|
||||
def config_edit():
|
||||
|
|
@ -1506,6 +1506,10 @@ config_cmd.parser.add_option(
|
|||
'-d', '--defaults', action='store_true',
|
||||
help='include the default configuration'
|
||||
)
|
||||
config_cmd.parser.add_option(
|
||||
'-r', '--redacted', action='store_true',
|
||||
help='redact sensitive fields'
|
||||
)
|
||||
config_cmd.func = config_func
|
||||
default_commands.append(config_cmd)
|
||||
|
||||
|
|
|
|||
|
|
@ -387,6 +387,7 @@ class RootView(ConfigView):
|
|||
"""
|
||||
self.sources = list(sources)
|
||||
self.name = ROOT_NAME
|
||||
self.redacted_fields = set()
|
||||
|
||||
def add(self, obj):
|
||||
self.sources.append(ConfigSource.of(obj))
|
||||
|
|
@ -404,6 +405,12 @@ class RootView(ConfigView):
|
|||
def root(self):
|
||||
return self
|
||||
|
||||
def add_redacted_fields(self, field_names):
|
||||
if not isinstance(field_names, list):
|
||||
field_names = [field_names]
|
||||
|
||||
self.redacted_fields = self.redacted_fields | set(field_names)
|
||||
|
||||
|
||||
class Subview(ConfigView):
|
||||
"""A subview accessed via a subscript of a parent view."""
|
||||
|
|
@ -455,6 +462,9 @@ class Subview(ConfigView):
|
|||
def root(self):
|
||||
return self.parent.root()
|
||||
|
||||
def add_redacted_fields(self, field_names):
|
||||
self.parent.add_redacted_fields(field_names)
|
||||
|
||||
|
||||
# Config file paths, including platform-specific paths and in-package
|
||||
# defaults.
|
||||
|
|
@ -585,11 +595,16 @@ def load_yaml(filename):
|
|||
|
||||
|
||||
# YAML dumping.
|
||||
REDACTED_FIELDS = ['password', 'username', 'userid', 'apikey',
|
||||
'apisecret', 'email', ]
|
||||
|
||||
|
||||
class Dumper(yaml.SafeDumper):
|
||||
"""A PyYAML Dumper that represents OrderedDicts as ordinary mappings
|
||||
(in order, of course).
|
||||
"""
|
||||
redacted_fields = set(REDACTED_FIELDS)
|
||||
|
||||
# From http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py
|
||||
def represent_mapping(self, tag, mapping, flow_style=None):
|
||||
value = []
|
||||
|
|
@ -600,6 +615,8 @@ class Dumper(yaml.SafeDumper):
|
|||
if hasattr(mapping, 'items'):
|
||||
mapping = list(mapping.items())
|
||||
for item_key, item_value in mapping:
|
||||
if item_key in Dumper.redacted_fields:
|
||||
item_value = u'REDACTED'
|
||||
node_key = self.represent_data(item_key)
|
||||
node_value = self.represent_data(item_value)
|
||||
if not (isinstance(node_key, yaml.ScalarNode) and
|
||||
|
|
@ -783,7 +800,7 @@ class Configuration(RootView):
|
|||
filename = os.path.abspath(filename)
|
||||
self.set(ConfigSource(load_yaml(filename), filename))
|
||||
|
||||
def dump(self, full=True):
|
||||
def dump(self, full=True, redact_fields=True):
|
||||
"""Dump the Configuration object to a YAML file.
|
||||
|
||||
The order of the keys is determined from the default
|
||||
|
|
@ -803,6 +820,12 @@ class Configuration(RootView):
|
|||
sources = [s for s in self.sources if not s.default]
|
||||
out_dict = RootView(sources).flatten()
|
||||
|
||||
if redact_fields:
|
||||
Dumper.redacted_fields = Dumper.redacted_fields | \
|
||||
self.redacted_fields
|
||||
else:
|
||||
Dumper.redacted_fields = set()
|
||||
|
||||
yaml_out = yaml.dump(out_dict, Dumper=Dumper,
|
||||
default_flow_style=None, indent=4,
|
||||
width=1000)
|
||||
|
|
|
|||
Loading…
Reference in a new issue