2017-12-16 23:03:03 +01:00
|
|
|
import os.path
|
2019-04-15 18:27:22 +02:00
|
|
|
import pkg_resources
|
2017-12-16 23:03:03 +01:00
|
|
|
import yaml
|
|
|
|
|
2020-08-29 20:12:02 +02:00
|
|
|
NAME = "phi"
|
2017-12-16 23:03:03 +01:00
|
|
|
|
2019-04-15 18:27:22 +02:00
|
|
|
DEFAULT_CONFIG = {
|
2022-02-03 00:46:45 +01:00
|
|
|
"core": {"listen": {"host": "localhost", "port": 8080}, "cookiestore_secret": None},
|
2019-04-15 18:27:22 +02:00
|
|
|
"ldap": {
|
|
|
|
"host": "localhost",
|
|
|
|
"port": 389,
|
|
|
|
"encryption": "TLSv1.2",
|
|
|
|
"ciphers": "HIGH",
|
|
|
|
"validate": True,
|
|
|
|
"ca_certs": pkg_resources.resource_filename(NAME, "openldap/cert.pem"),
|
|
|
|
"base_dn": None,
|
|
|
|
"attribute_mail": "mail",
|
|
|
|
},
|
|
|
|
"logging": {
|
|
|
|
"version": 1,
|
2020-08-29 20:12:02 +02:00
|
|
|
"formatters": {"default": {"format": "[%(name)s %(levelname)s] %(message)s"}},
|
2019-04-15 18:27:22 +02:00
|
|
|
"handlers": {
|
|
|
|
"console": {
|
|
|
|
"class": "logging.StreamHandler",
|
|
|
|
"formatter": "default",
|
|
|
|
"stream": "ext://sys.stdout",
|
|
|
|
},
|
|
|
|
"file": {
|
|
|
|
"class": "logging.FileHandler",
|
|
|
|
"formatter": "default",
|
|
|
|
"filename": "phi.log",
|
2020-08-29 20:12:02 +02:00
|
|
|
},
|
2019-04-15 18:27:22 +02:00
|
|
|
},
|
|
|
|
"loggers": {
|
2022-02-03 00:46:45 +01:00
|
|
|
"phi": {
|
|
|
|
"level": "INFO",
|
|
|
|
"handlers": ["console", "file"],
|
|
|
|
},
|
|
|
|
"aiohttp": {
|
|
|
|
"level": "INFO",
|
|
|
|
"handlers": ["console", "file"],
|
|
|
|
},
|
|
|
|
"bonsai": {
|
|
|
|
"level": "WARNING",
|
|
|
|
"handlers": ["console", "file"],
|
|
|
|
},
|
2020-08-29 20:12:02 +02:00
|
|
|
},
|
|
|
|
},
|
2019-04-15 18:27:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
DUMMY_CONFIG = {"core": {}, "ldap": {}, "logging": {}}
|
|
|
|
|
2020-08-29 20:12:02 +02:00
|
|
|
CONFIG_FILE = "config.yml"
|
|
|
|
CONFIG_PATHS = [
|
|
|
|
"./",
|
|
|
|
"~/.config/" + NAME + "/",
|
|
|
|
"/usr/local/etc/" + NAME + "/",
|
|
|
|
"/etc/" + NAME + "/",
|
|
|
|
]
|
|
|
|
CONFIG_FILES = [os.path.join(p, CONFIG_FILE) for p in CONFIG_PATHS]
|
2017-12-16 23:03:03 +01:00
|
|
|
|
|
|
|
|
2019-04-15 18:27:22 +02:00
|
|
|
def get_config(config_path=None):
|
2017-12-16 23:03:03 +01:00
|
|
|
"""Return the path of the found configuration file and its content
|
|
|
|
|
2019-04-15 18:27:22 +02:00
|
|
|
:param config_path: optional path to a config file.
|
|
|
|
|
2017-12-16 23:03:03 +01:00
|
|
|
:returns: (path, config)
|
|
|
|
:rtype: (str, dict)
|
|
|
|
"""
|
2019-04-15 18:27:22 +02:00
|
|
|
if config_path:
|
|
|
|
with open(config_path) as c:
|
|
|
|
config = yaml.safe_load(c)
|
|
|
|
return config_path, config
|
2017-12-16 23:03:03 +01:00
|
|
|
for f in CONFIG_FILES:
|
|
|
|
try:
|
2020-08-29 20:12:02 +02:00
|
|
|
with open(f, "r") as c:
|
2017-12-16 23:03:03 +01:00
|
|
|
config = yaml.safe_load(c)
|
|
|
|
return (f, config)
|
|
|
|
except FileNotFoundError:
|
|
|
|
# Skip to the next file.
|
|
|
|
# We only care if the file is preset but it's not
|
|
|
|
# accessible or if the file is not present at all
|
|
|
|
# in any of CONFIG_PATHS.
|
|
|
|
pass
|
2019-04-15 18:27:22 +02:00
|
|
|
return None, DUMMY_CONFIG
|
|
|
|
|
|
|
|
|
|
|
|
def merge_config(cli_config, file_config):
|
|
|
|
"""
|
|
|
|
Merge the cli-provided and file-provided config.
|
|
|
|
"""
|
|
|
|
return recursive_merge(cli_config, file_config)
|
|
|
|
|
|
|
|
|
|
|
|
def _init_with_shape_of(element):
|
|
|
|
if isinstance(element, dict):
|
|
|
|
return {}
|
|
|
|
elif isinstance(element, list):
|
|
|
|
return []
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def recursive_merge(main_config, aux_config):
|
|
|
|
def _recursive_merge(main, aux, default, key, _ret_config):
|
|
|
|
if isinstance(default, dict):
|
|
|
|
_sub_conf = {}
|
|
|
|
for k, v in default.items():
|
|
|
|
_main = main[k] if k in main else _init_with_shape_of(v)
|
|
|
|
_aux = aux[k] if k in aux else _init_with_shape_of(v)
|
|
|
|
_recursive_merge(_main, _aux, v, k, _sub_conf)
|
|
|
|
_ret_config[key] = _sub_conf
|
|
|
|
elif isinstance(default, list):
|
|
|
|
_main = main.copy()
|
|
|
|
if aux is not None:
|
|
|
|
_main.extend(aux)
|
|
|
|
_ret_config[key] = list(set(_main))
|
2020-08-29 20:06:08 +02:00
|
|
|
elif isinstance(default, bool):
|
|
|
|
_ret_config[key] = default and aux and main
|
2019-04-15 18:27:22 +02:00
|
|
|
else:
|
|
|
|
if main is not None:
|
|
|
|
_ret_config[key] = main
|
|
|
|
elif aux is not None:
|
|
|
|
_ret_config[key] = aux
|
|
|
|
else:
|
|
|
|
_ret_config[key] = default
|
|
|
|
|
|
|
|
_config = {}
|
|
|
|
_recursive_merge(main_config, aux_config, DEFAULT_CONFIG, "ROOT", _config)
|
|
|
|
|
|
|
|
return _config["ROOT"]
|
2022-02-03 00:46:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
def extract_secret(config):
|
|
|
|
try:
|
|
|
|
secret = config["core"]["cookiestore_secret"].encode("utf-8")
|
|
|
|
if len(secret) != 32:
|
|
|
|
raise ValueError(
|
|
|
|
"The provided core.cookiestore_secret must be 32 bytes long"
|
|
|
|
)
|
|
|
|
|
|
|
|
return secret
|
|
|
|
except KeyError:
|
|
|
|
raise RuntimeError("You must provide a core.cookiestore_secret")
|