Sunil Mohan Adapa 144c8c9d95
cfg, frontpage: Ignore errors while reading config and shortcuts
- Ignore errors while trying to expand a file path list into its .d components
path list.

- Ignore errors when reading shortcuts fails a file for any reason.

- Errors when reading configuration file already ignored. os.path.isfile() and
configparser.Configparser.read() do not raise an exception under any
circumstances.

Analysis:

Regression in 20.12 reported at
https://discuss.freedombox.org/t/fb-20-12-solved-plinth-fails-to-start-due-to-new-frontpage-py-shortcuts-and-filesystem-permissions/994/4

- freedom-maker creates /var/lib/freedombox/ with mode 755 as root but this only
applies for disk images.

- freedombox.postinst, networks, apache check for the existence of
/var/lib/freedombox/is-freedombox-disk-image .

- Samba creates /var/lib/freedombox with mode 755 as root.

- Backups creates /var/lib/freedombox/borgbackup but not the parent directory?

- Shortcuts are now read from /var/lib/freedombox/.

Tests performed:

- Create directories /var/lib/freedombox and /etc/freedombox with permission set
to 750. In case of configuration, an early warning message is printed and in
case of shortcuts warnings are printed but service starts properly. Changing the
permission to 755 removes the warnings.

- Ensure 755 permission on above two directories. Create non-empty files
custom-shortcuts.json and freedombox.config with permissions 640. In case of
config no warning is printed (silently ignored) and in case of shortcuts,
warning is printed that file could not be read but service starts properly.
Changing the permission to 644, no warnings are printed.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2020-07-05 15:29:36 -04:00

142 lines
4.3 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configuration parser and default values for configuration options.
"""
import configparser
import logging
import os
import pathlib
logger = logging.getLogger(__name__)
# [Path] section
file_root = '/usr/share/plinth'
config_dir = '/etc/plinth'
data_dir = '/var/lib/plinth'
custom_static_dir = '/var/www/plinth/custom/static'
store_file = data_dir + '/plinth.sqlite3'
actions_dir = '/usr/share/plinth/actions'
doc_dir = '/usr/share/freedombox'
server_dir = '/plinth'
# [Network] section
host = '127.0.0.1'
port = 8000
# Enable the following only if Plinth is behind a proxy server. The
# proxy server should properly clean and the following HTTP headers:
# X-Forwarded-For
# X-Forwarded-Host
# X-Forwarded-Proto
# If you enable these unnecessarily, this will lead to serious security
# problems. For more information, see
# https://docs.djangoproject.com/en/1.7/ref/settings/
#
# These are enabled by default in FreedomBox because the default
# configuration allows only connections from localhost
#
# Leave the values blank to disable
use_x_forwarded_for = True
use_x_forwarded_host = True
secure_proxy_ssl_header = 'HTTP_X_FORWARDED_PROTO'
# [Misc] section
box_name = 'FreedomBox'
# Other globals
develop = False
config_files = []
def expand_to_dot_d_paths(file_paths):
"""Expand a list of file paths to include file.d/* also."""
final_list = []
for file_path in file_paths:
final_list.append(str(file_path))
path = pathlib.Path(file_path)
path_d = path.with_suffix(path.suffix + '.d')
try:
for dot_d_file in sorted(path_d.glob('*' + path.suffix)):
final_list.append(str(dot_d_file))
except Exception as exception:
logger.warning('Unable to read from directory %s: %s', path_d,
exception)
return final_list
def get_develop_config_path():
"""Return config path of current source folder for development mode."""
root_directory = os.path.dirname(os.path.realpath(__file__))
root_directory = os.path.realpath(root_directory)
config_path = os.path.join(root_directory, 'develop.config')
return config_path
def get_config_paths():
"""Get default config paths."""
return [
'/usr/share/freedombox/freedombox.config',
'/etc/plinth/plinth.config',
'/etc/freedombox/freedombox.config',
]
def read():
"""Read all configuration files."""
config_paths = get_config_paths()
for config_path in expand_to_dot_d_paths(config_paths):
read_file(config_path)
def read_file(config_path):
"""Read and merge into defaults a single configuration file."""
if not os.path.isfile(config_path): # Does not throw exceptions
# Ignore missing configuration files
return
# Keep a note of configuration files read.
config_files.append(config_path)
parser = configparser.ConfigParser(
defaults={
'parent_dir':
pathlib.Path(config_path).parent.resolve(),
'parent_parent_dir':
pathlib.Path(config_path).parent.parent.resolve(),
})
parser.read(config_path) # Ignores all read errors
config_items = (
('Path', 'file_root', 'string'),
('Path', 'config_dir', 'string'),
('Path', 'data_dir', 'string'),
('Path', 'custom_static_dir', 'string'),
('Path', 'store_file', 'string'),
('Path', 'actions_dir', 'string'),
('Path', 'doc_dir', 'string'),
('Path', 'server_dir', 'string'),
('Network', 'host', 'string'),
('Network', 'port', 'int'),
('Network', 'secure_proxy_ssl_header', 'string'),
('Network', 'use_x_forwarded_for', 'bool'),
('Network', 'use_x_forwarded_host', 'bool'),
('Misc', 'box_name', 'string'),
)
for section, name, datatype in config_items:
try:
value = parser.get(section, name)
except (configparser.NoSectionError, configparser.NoOptionError):
# Use default values for any missing keys in configuration
continue
else:
if datatype == 'int':
value = int(value)
elif datatype == 'bool':
value = (value.lower() == 'true')
globals()[name] = value