2021-07-01 09:04:57 +00:00
|
|
|
import datetime
|
2024-10-21 09:17:55 +00:00
|
|
|
import logging
|
2024-03-29 18:31:01 +00:00
|
|
|
import sys
|
2021-12-20 22:57:27 +00:00
|
|
|
from logging.config import dictConfig
|
2024-03-15 18:55:12 +00:00
|
|
|
from logging.config import fileConfig
|
2020-08-17 13:49:48 +00:00
|
|
|
|
2021-12-20 22:57:27 +00:00
|
|
|
from flask import Flask
|
2023-06-23 14:26:38 +00:00
|
|
|
from flask import request
|
2021-12-20 22:57:27 +00:00
|
|
|
from flask import session
|
2023-03-28 18:30:29 +00:00
|
|
|
from flask_wtf.csrf import CSRFProtect
|
2020-09-01 15:11:30 +00:00
|
|
|
|
2023-03-28 18:30:29 +00:00
|
|
|
csrf = CSRFProtect()
|
2020-08-17 13:49:48 +00:00
|
|
|
|
|
|
|
|
2022-11-20 21:34:05 +00:00
|
|
|
def setup_sentry(app): # pragma: no cover
|
2023-12-18 17:06:03 +00:00
|
|
|
if not app.config["CANAILLE"]["SENTRY_DSN"]:
|
2022-01-11 17:02:23 +00:00
|
|
|
return None
|
|
|
|
|
2022-11-20 21:34:05 +00:00
|
|
|
try:
|
2022-01-11 17:02:23 +00:00
|
|
|
import sentry_sdk
|
|
|
|
from sentry_sdk.integrations.flask import FlaskIntegration
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
return None
|
|
|
|
|
2023-12-18 17:06:03 +00:00
|
|
|
sentry_sdk.init(
|
|
|
|
dsn=app.config["CANAILLE"]["SENTRY_DSN"], integrations=[FlaskIntegration()]
|
|
|
|
)
|
2022-01-11 17:02:23 +00:00
|
|
|
return sentry_sdk
|
|
|
|
|
|
|
|
|
2021-10-31 13:40:12 +00:00
|
|
|
def setup_logging(app):
|
2023-12-18 17:06:03 +00:00
|
|
|
conf = app.config["CANAILLE"]["LOGGING"]
|
2024-10-21 09:17:55 +00:00
|
|
|
|
|
|
|
security_level_name = "SECURITY"
|
|
|
|
if not hasattr(logging, security_level_name):
|
|
|
|
addLoggingLevel(security_level_name, logging.INFO + 5)
|
|
|
|
|
2024-03-15 18:55:12 +00:00
|
|
|
if conf is None:
|
|
|
|
log_level = "DEBUG" if app.debug else "INFO"
|
|
|
|
dictConfig(
|
|
|
|
{
|
|
|
|
"version": 1,
|
|
|
|
"formatters": {
|
|
|
|
"default": {
|
|
|
|
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"handlers": {
|
|
|
|
"wsgi": {
|
|
|
|
"class": "logging.StreamHandler",
|
|
|
|
"stream": "ext://flask.logging.wsgi_errors_stream",
|
|
|
|
"formatter": "default",
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"root": {"level": log_level, "handlers": ["wsgi"]},
|
|
|
|
"loggers": {
|
|
|
|
"faker": {"level": "WARNING"},
|
|
|
|
"mail.log": {"level": "WARNING"},
|
|
|
|
},
|
|
|
|
"disable_existing_loggers": False,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
elif isinstance(conf, dict):
|
|
|
|
dictConfig(conf)
|
|
|
|
|
2021-10-31 13:40:12 +00:00
|
|
|
else:
|
2024-03-15 18:55:12 +00:00
|
|
|
fileConfig(conf, disable_existing_loggers=False)
|
2021-10-31 13:40:12 +00:00
|
|
|
|
|
|
|
|
2021-12-08 17:06:50 +00:00
|
|
|
def setup_jinja(app):
|
2021-12-10 16:08:43 +00:00
|
|
|
app.jinja_env.filters["len"] = len
|
2023-03-22 18:29:28 +00:00
|
|
|
app.jinja_env.policies["ext.i18n.trimmed"] = True
|
2021-12-08 17:06:50 +00:00
|
|
|
|
|
|
|
|
2021-12-08 15:10:01 +00:00
|
|
|
def setup_blueprints(app):
|
2023-12-25 23:23:47 +00:00
|
|
|
import canaille.core.endpoints
|
2022-01-05 15:30:46 +00:00
|
|
|
|
2021-12-08 15:10:01 +00:00
|
|
|
app.url_map.strict_slashes = False
|
|
|
|
|
2023-12-25 23:23:47 +00:00
|
|
|
app.register_blueprint(canaille.core.endpoints.bp)
|
2023-09-15 15:24:05 +00:00
|
|
|
|
2023-12-18 17:06:03 +00:00
|
|
|
if "CANAILLE_OIDC" in app.config:
|
2023-12-25 23:23:47 +00:00
|
|
|
import canaille.oidc.endpoints
|
2023-09-15 15:24:05 +00:00
|
|
|
|
2023-12-25 23:23:47 +00:00
|
|
|
app.register_blueprint(canaille.oidc.endpoints.bp)
|
2020-09-27 15:32:23 +00:00
|
|
|
|
|
|
|
|
2023-03-28 18:30:29 +00:00
|
|
|
def setup_flask(app):
|
|
|
|
csrf.init_app(app)
|
|
|
|
|
|
|
|
@app.before_request
|
|
|
|
def make_session_permanent():
|
|
|
|
session.permanent = True
|
|
|
|
app.permanent_session_lifetime = datetime.timedelta(days=365)
|
|
|
|
|
|
|
|
@app.context_processor
|
|
|
|
def global_processor():
|
2023-04-09 13:52:55 +00:00
|
|
|
from canaille.app.flask import current_user
|
2023-03-28 18:30:29 +00:00
|
|
|
|
|
|
|
return {
|
2023-07-10 18:03:13 +00:00
|
|
|
"debug": app.debug or app.config.get("TESTING", False),
|
2023-12-18 17:06:03 +00:00
|
|
|
"logo_url": app.config["CANAILLE"]["LOGO"],
|
|
|
|
"favicon_url": app.config["CANAILLE"]["FAVICON"]
|
|
|
|
or app.config["CANAILLE"]["LOGO"],
|
|
|
|
"website_name": app.config["CANAILLE"]["NAME"],
|
2023-03-28 18:30:29 +00:00
|
|
|
"user": current_user(),
|
|
|
|
"menu": True,
|
2023-06-23 14:26:38 +00:00
|
|
|
"is_boosted": request.headers.get("HX-Boosted", False),
|
2024-05-14 20:53:47 +00:00
|
|
|
"features": app.features,
|
2023-03-28 18:30:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-06-28 15:56:49 +00:00
|
|
|
def setup_flask_converters(app):
|
|
|
|
from canaille.app import models
|
2024-03-15 18:58:06 +00:00
|
|
|
from canaille.app.flask import model_converter
|
2023-06-28 15:56:49 +00:00
|
|
|
|
2023-04-09 21:00:13 +00:00
|
|
|
for model_name, model_class in models.MODELS.items():
|
|
|
|
app.url_map.converters[model_name.lower()] = model_converter(model_class)
|
2023-06-28 15:56:49 +00:00
|
|
|
|
|
|
|
|
2024-04-22 16:10:49 +00:00
|
|
|
def create_app(
|
|
|
|
config=None, validate=True, backend=None, env_file=".env", env_prefix=""
|
|
|
|
):
|
2023-06-15 16:29:12 +00:00
|
|
|
from .app.configuration import setup_config
|
2024-05-14 20:53:47 +00:00
|
|
|
from .app.features import setup_features
|
2024-03-15 18:58:06 +00:00
|
|
|
from .app.i18n import setup_i18n
|
2023-08-16 15:14:11 +00:00
|
|
|
from .app.themes import setup_themer
|
2023-04-09 21:00:13 +00:00
|
|
|
from .backends import setup_backend
|
2023-06-15 16:29:12 +00:00
|
|
|
|
2021-12-08 15:10:01 +00:00
|
|
|
app = Flask(__name__)
|
2023-07-10 16:45:54 +00:00
|
|
|
with app.app_context():
|
2024-04-22 16:10:49 +00:00
|
|
|
if not setup_config(
|
|
|
|
app=app,
|
|
|
|
config=config,
|
|
|
|
test_config=validate,
|
|
|
|
env_file=env_file,
|
|
|
|
env_prefix=env_prefix,
|
|
|
|
): # pragma: no cover
|
2024-03-29 18:31:01 +00:00
|
|
|
sys.exit(1)
|
2021-12-08 15:10:01 +00:00
|
|
|
|
2022-01-11 17:02:23 +00:00
|
|
|
sentry_sdk = setup_sentry(app)
|
2021-12-08 15:10:01 +00:00
|
|
|
try:
|
|
|
|
setup_logging(app)
|
2023-12-25 12:22:43 +00:00
|
|
|
backend = setup_backend(app, backend)
|
2024-05-14 20:53:47 +00:00
|
|
|
setup_features(app)
|
2023-06-28 15:56:49 +00:00
|
|
|
setup_flask_converters(app)
|
2021-12-08 15:10:01 +00:00
|
|
|
setup_blueprints(app)
|
2021-12-08 17:06:50 +00:00
|
|
|
setup_jinja(app)
|
2022-11-20 21:12:18 +00:00
|
|
|
setup_i18n(app)
|
2021-12-08 15:10:01 +00:00
|
|
|
setup_themer(app)
|
2023-03-28 18:30:29 +00:00
|
|
|
setup_flask(app)
|
2020-09-27 15:32:23 +00:00
|
|
|
|
2023-12-18 17:06:03 +00:00
|
|
|
if "CANAILLE_OIDC" in app.config:
|
2023-09-15 15:24:05 +00:00
|
|
|
from .oidc.oauth import setup_oauth
|
|
|
|
|
|
|
|
setup_oauth(app)
|
|
|
|
|
2022-11-20 21:34:05 +00:00
|
|
|
except Exception as exc: # pragma: no cover
|
2022-01-11 17:02:23 +00:00
|
|
|
if sentry_sdk:
|
2020-09-29 16:21:41 +00:00
|
|
|
sentry_sdk.capture_exception(exc)
|
|
|
|
raise
|
2021-12-06 20:49:38 +00:00
|
|
|
|
|
|
|
return app
|
2024-10-21 09:17:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
def addLoggingLevel(levelName, levelNum, methodName=None):
|
|
|
|
"""Comprehensively adds a new logging level to the `logging` module and the
|
|
|
|
currently configured logging class.
|
|
|
|
|
|
|
|
`levelName` becomes an attribute of the `logging` module with the value
|
|
|
|
`levelNum`. `methodName` becomes a convenience method for both `logging`
|
|
|
|
itself and the class returned by `logging.getLoggerClass()` (usually just
|
|
|
|
`logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
|
|
|
|
used.
|
|
|
|
|
|
|
|
To avoid accidental clobberings of existing attributes, this method will
|
|
|
|
raise an `AttributeError` if the level name is already an attribute of the
|
|
|
|
`logging` module or if the method name is already present
|
|
|
|
|
|
|
|
Example
|
|
|
|
-------
|
|
|
|
>>> addLoggingLevel("TRACE", logging.DEBUG - 5)
|
|
|
|
>>> logging.getLogger(__name__).setLevel("TRACE")
|
|
|
|
>>> logging.getLogger(__name__).trace("that worked")
|
|
|
|
>>> logging.trace("so did this")
|
|
|
|
>>> logging.TRACE
|
|
|
|
5
|
|
|
|
"""
|
|
|
|
if not methodName:
|
|
|
|
methodName = levelName.lower()
|
|
|
|
|
|
|
|
if hasattr(logging, levelName):
|
|
|
|
raise AttributeError(f"{levelName} already defined in logging module")
|
|
|
|
if hasattr(logging, methodName):
|
|
|
|
raise AttributeError(f"{methodName} already defined in logging module")
|
|
|
|
if hasattr(logging.getLoggerClass(), methodName):
|
|
|
|
raise AttributeError(f"{methodName} already defined in logger class")
|
|
|
|
|
|
|
|
# This method was inspired by the answers to Stack Overflow post
|
|
|
|
# http://stackoverflow.com/q/2183233/2988730, especially
|
|
|
|
# http://stackoverflow.com/a/13638084/2988730
|
|
|
|
def logForLevel(self, message, *args, **kwargs):
|
|
|
|
if self.isEnabledFor(levelNum):
|
|
|
|
self._log(levelNum, message, args, **kwargs)
|
|
|
|
|
|
|
|
def logToRoot(message, *args, **kwargs):
|
|
|
|
logging.log(levelNum, message, *args, **kwargs)
|
|
|
|
|
|
|
|
logging.addLevelName(levelNum, levelName)
|
|
|
|
setattr(logging, levelName, levelNum)
|
|
|
|
setattr(logging.getLoggerClass(), methodName, logForLevel)
|
|
|
|
setattr(logging, methodName, logToRoot)
|