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/>`_,
|
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>`_.
|
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
|
Removed
|
||||||
*******
|
*******
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import datetime
|
||||||
import os
|
import os
|
||||||
from logging.config import dictConfig
|
from logging.config import dictConfig
|
||||||
|
|
||||||
import toml
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask import session
|
from flask import session
|
||||||
|
@ -15,34 +14,6 @@ from flask_wtf.csrf import CSRFProtect
|
||||||
csrf = 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):
|
def setup_backend(app, backend):
|
||||||
from .backends.ldap.backend import Backend
|
from .backends.ldap.backend import Backend
|
||||||
|
|
||||||
|
@ -182,14 +153,15 @@ def setup_flask(app):
|
||||||
|
|
||||||
|
|
||||||
def create_app(config=None, validate=True, backend=None):
|
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__)
|
app = Flask(__name__)
|
||||||
setup_config(app, config, validate)
|
setup_config(app, config, validate)
|
||||||
|
|
||||||
sentry_sdk = setup_sentry(app)
|
sentry_sdk = setup_sentry(app)
|
||||||
try:
|
try:
|
||||||
from .oidc.oauth import setup_oauth
|
|
||||||
from .app.i18n import setup_i18n
|
|
||||||
|
|
||||||
setup_logging(app)
|
setup_logging(app)
|
||||||
setup_backend(app, backend)
|
setup_backend(app, backend)
|
||||||
setup_oauth(app)
|
setup_oauth(app)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import os
|
import os
|
||||||
import smtplib
|
import smtplib
|
||||||
import socket
|
import socket
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
|
import toml
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
@ -9,6 +12,58 @@ class ConfigurationException(Exception):
|
||||||
pass
|
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):
|
def validate(config, validate_remote=False):
|
||||||
validate_keypair(config)
|
validate_keypair(config)
|
||||||
validate_theme(config)
|
validate_theme(config)
|
||||||
|
|
|
@ -6,14 +6,18 @@ Here are the different options you can have in your configuration file.
|
||||||
.. contents::
|
.. contents::
|
||||||
:local:
|
: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
|
Sections
|
||||||
========
|
========
|
||||||
|
|
||||||
Miscellaneous
|
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:
|
:SECRET_KEY:
|
||||||
**Required.** The Flask secret key. You should set a random string here.
|
**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
|
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):
|
def test_smtp_connection_remote_smtp_unreachable(testclient, backend, configuration):
|
||||||
configuration["SMTP"]["HOST"] = "smtp://invalid-smtp.com"
|
configuration["SMTP"]["HOST"] = "smtp://invalid-smtp.com"
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
|
|
Loading…
Reference in a new issue