forked from Github-Mirrors/canaille
Configuration entries can be loaded from files.
Co-authored-by: Sofi <sofi+git@mailbox.org>
This commit is contained in:
parent
f254cd94f1
commit
a9d9d43152
5 changed files with 85 additions and 35 deletions
|
@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_,
|
||||
and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
|
||||
|
||||
Added
|
||||
*****
|
||||
|
||||
- Configuration entries can be loaded from files if the entry key has a *_FILE* suffix
|
||||
and the entry value is the path to the file. :issue:`134` :pr:`134`
|
||||
|
||||
Removed
|
||||
*******
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import datetime
|
|||
import os
|
||||
from logging.config import dictConfig
|
||||
|
||||
import toml
|
||||
from flask import Flask
|
||||
from flask import g
|
||||
from flask import session
|
||||
|
@ -15,34 +14,6 @@ from flask_wtf.csrf import CSRFProtect
|
|||
csrf = CSRFProtect()
|
||||
|
||||
|
||||
def setup_config(app, config=None, validate=True):
|
||||
import canaille.app.configuration
|
||||
from canaille.oidc.installation import install
|
||||
|
||||
app.config.from_mapping(
|
||||
{
|
||||
"SESSION_COOKIE_NAME": "canaille",
|
||||
"OAUTH2_REFRESH_TOKEN_GENERATOR": True,
|
||||
"OAUTH2_ACCESS_TOKEN_GENERATOR": "canaille.oidc.oauth.generate_access_token",
|
||||
}
|
||||
)
|
||||
if config:
|
||||
app.config.from_mapping(config)
|
||||
elif "CONFIG" in os.environ:
|
||||
app.config.from_mapping(toml.load(os.environ.get("CONFIG")))
|
||||
else:
|
||||
raise Exception(
|
||||
"No configuration file found. "
|
||||
"Either create conf/config.toml or set the 'CONFIG' variable environment."
|
||||
)
|
||||
|
||||
if app.debug: # pragma: no cover
|
||||
install(app.config)
|
||||
|
||||
if validate:
|
||||
canaille.app.configuration.validate(app.config)
|
||||
|
||||
|
||||
def setup_backend(app, backend):
|
||||
from .backends.ldap.backend import Backend
|
||||
|
||||
|
@ -182,14 +153,15 @@ def setup_flask(app):
|
|||
|
||||
|
||||
def create_app(config=None, validate=True, backend=None):
|
||||
from .oidc.oauth import setup_oauth
|
||||
from .app.i18n import setup_i18n
|
||||
from .app.configuration import setup_config
|
||||
|
||||
app = Flask(__name__)
|
||||
setup_config(app, config, validate)
|
||||
|
||||
sentry_sdk = setup_sentry(app)
|
||||
try:
|
||||
from .oidc.oauth import setup_oauth
|
||||
from .app.i18n import setup_i18n
|
||||
|
||||
setup_logging(app)
|
||||
setup_backend(app, backend)
|
||||
setup_oauth(app)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import os
|
||||
import smtplib
|
||||
import socket
|
||||
from collections.abc import Mapping
|
||||
|
||||
import toml
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
@ -9,6 +12,58 @@ class ConfigurationException(Exception):
|
|||
pass
|
||||
|
||||
|
||||
def parse_file_keys(config):
|
||||
"""
|
||||
Replaces configuration entries with the '_FILE' suffix with
|
||||
the matching file content.
|
||||
"""
|
||||
|
||||
SUFFIX = "_FILE"
|
||||
new_config = {}
|
||||
for key, value in config.items():
|
||||
if isinstance(value, Mapping):
|
||||
new_config[key] = parse_file_keys(value)
|
||||
|
||||
elif isinstance(key, str) and key.endswith(SUFFIX) and isinstance(value, str):
|
||||
with open(value) as f:
|
||||
value = f.read().rstrip("\n")
|
||||
|
||||
root_key = key[: -len(SUFFIX)]
|
||||
new_config[root_key] = value
|
||||
|
||||
else:
|
||||
new_config[key] = value
|
||||
|
||||
return new_config
|
||||
|
||||
|
||||
def setup_config(app, config=None, validate_config=True):
|
||||
from canaille.oidc.installation import install
|
||||
|
||||
app.config.from_mapping(
|
||||
{
|
||||
"SESSION_COOKIE_NAME": "canaille",
|
||||
"OAUTH2_REFRESH_TOKEN_GENERATOR": True,
|
||||
"OAUTH2_ACCESS_TOKEN_GENERATOR": "canaille.oidc.oauth.generate_access_token",
|
||||
}
|
||||
)
|
||||
if config:
|
||||
app.config.from_mapping(parse_file_keys(config))
|
||||
elif "CONFIG" in os.environ:
|
||||
app.config.from_mapping(parse_file_keys(toml.load(os.environ.get("CONFIG"))))
|
||||
else:
|
||||
raise Exception(
|
||||
"No configuration file found. "
|
||||
"Either create conf/config.toml or set the 'CONFIG' variable environment."
|
||||
)
|
||||
|
||||
if app.debug: # pragma: no cover
|
||||
install(app.config)
|
||||
|
||||
if validate_config:
|
||||
validate(app.config)
|
||||
|
||||
|
||||
def validate(config, validate_remote=False):
|
||||
validate_keypair(config)
|
||||
validate_theme(config)
|
||||
|
|
|
@ -6,14 +6,18 @@ Here are the different options you can have in your configuration file.
|
|||
.. contents::
|
||||
:local:
|
||||
|
||||
.. note ::
|
||||
|
||||
Any configuration entry can be suffixed by *_FILE* and point to the path of
|
||||
a file that contains the actual value. For instance you could have
|
||||
``SECRET_KEY_FILE = "/path/to/secret.txt"`` instead of ``SECRET_KEY = "very-secret"``
|
||||
|
||||
Sections
|
||||
========
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
Canaille is based on Flask, so any `flask configuration <https://flask.palletsprojects.com/en/1.1.x/config/#builtin-configuration-values>`_ option will be usable with canaille:
|
||||
|
||||
|
||||
Canaille is based on Flask, so any `flask configuration <https://flask.palletsprojects.com/en/2.3.x/config/#builtin-configuration-values>`_ option will be usable with canaille:
|
||||
|
||||
:SECRET_KEY:
|
||||
**Required.** The Flask secret key. You should set a random string here.
|
||||
|
|
|
@ -7,6 +7,19 @@ from canaille.app.configuration import validate
|
|||
from flask_webtest import TestApp
|
||||
|
||||
|
||||
def test_configuration_file_suffix(tmp_path, backend, configuration):
|
||||
file_path = os.path.join(tmp_path, "secret.txt")
|
||||
with open(file_path, "w") as fd:
|
||||
fd.write("very-secret")
|
||||
|
||||
del configuration["SECRET_KEY"]
|
||||
configuration["SECRET_KEY_FILE"] = file_path
|
||||
|
||||
app = create_app(configuration)
|
||||
assert "SECRET_KEY_FILE" not in app.config
|
||||
assert app.config["SECRET_KEY"] == "very-secret"
|
||||
|
||||
|
||||
def test_smtp_connection_remote_smtp_unreachable(testclient, backend, configuration):
|
||||
configuration["SMTP"]["HOST"] = "smtp://invalid-smtp.com"
|
||||
with pytest.raises(
|
||||
|
|
Loading…
Reference in a new issue