-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
210 lines (165 loc) · 7.55 KB
/
Copy pathapp.py
File metadata and controls
210 lines (165 loc) · 7.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
"""
===============================================================================
Project : openpass
Module : app.py
Created : 2025-10-17
Author : Florian
Purpose : This module defines the main application instance for the openpass
project. It initializes and configures a Flask application with HTTP
server middleware, CSRF protection, request limiter, OAuth support,
encryption, logging, and various blueprints for handling different
application routes and functionalities. The function also ensures
secure cookies and integrates various Flask extensions for enhancing
application behavior.
@docstyle: google
@language: english
@voice: imperative
===============================================================================
"""
# app.py
# Standard Library
import logging
import os
# Third-Party
from cryptography.fernet import Fernet
from flask import Flask, render_template, request, send_from_directory, abort
from flask_wtf import CSRFProtect
from werkzeug.middleware.proxy_fix import ProxyFix
# Local/Application
from core import extensions as ext
from core.config import load_config
from core.loggers import configure_logging
from core.middleware import csp_middleware
from core.oauth_client import init_oauth
csrf = CSRFProtect()
def create_app():
"""
Creates and configures the Flask application instance.
This function sets up the core configurations and integrations for the Flask
application, including proxy handling, configuration loading, branding-related
routes and context processors, CSRF protection, rate limiting, encryption,
extensions initialization, logging, CSP middleware, and application blueprints
registration. The application is secured with session cookies configured for
security and optional OAuth integration is also initialized.
Returns:
Flask: A fully configured Flask application instance.
Raises:
RuntimeError: If the required `FERNET_KEY` configuration is not set.
"""
app = Flask(__name__)
# Vertrauen in Proxy-Header für HTTPS, Host und Client-IP.
# x_for is required: without it request.remote_addr is the reverse proxy's address
# for every request, which makes the per-IP rate limits a single shared counter and
# renders the IP addresses in the auth log meaningless. The value is the number of
# trusted proxies in front of the app (Traefik = 1).
trusted_proxies = int(os.getenv("TRUSTED_PROXY_COUNT", 1))
app.wsgi_app = ProxyFix(
app.wsgi_app, x_for=trusted_proxies, x_proto=trusted_proxies, x_host=trusted_proxies
)
# load config
load_config(app)
from core.branding import load_branding, branding_file, branding_css
load_branding(app)
# Branding-Routen registrieren
app.add_url_rule("/branding/<path:filename>", view_func=branding_file)
app.add_url_rule("/branding/css/<path:filename>", view_func=branding_css)
#register branding injection
# Branding-Kontextprozessoren registrieren
from core.context_processors import inject_branding, inject_branding_colors, inject_is_admin
app.context_processor(inject_branding)
app.context_processor(inject_branding_colors)
app.context_processor(inject_is_admin)
# CSRF-Schutz aktivieren
csrf.init_app(app)
# Request Limiter Schutz. The instance lives in core.extensions and is only bound
# here — rebinding it would leave the blueprint modules holding the previous one,
# because they import the name at module load time to apply their decorators.
# Flask-Limiter reads the backend from RATELIMIT_STORAGE_URI, so the project's own
# RATE_LIMIT_STORAGE setting is mapped onto it.
app.config.setdefault(
"RATELIMIT_STORAGE_URI",
app.config.get("RATE_LIMIT_STORAGE", "redis://localhost:6379"),
)
ext.limiter.init_app(app)
# Mail extension initialisieren
ext.mail.init_app(app)
# Verschlüsselung
fernet_key = app.config.get("FERNET_KEY")
if not fernet_key:
raise RuntimeError("FERNET_KEY ist nicht gesetzt!")
ext.fernet = Fernet(fernet_key)
# Flask Login Manager
ext.login_manager.init_app(app)
ext.login_manager.login_view = "auth.login_page"
# Session cookie flags (Secure/HttpOnly/SameSite) and the session lifetime are
# configured centrally in core.config.load_config.
# OAuth atuhlib client für RC
init_oauth(app)
# Logger initialisieren (main, import, csp, auth)
configure_logging(app)
# CSP Middleware aktivieren
app = csp_middleware(app, report_only=True)
# Blueprints registrieren (auth, cards, profile)
from blueprints.auth import auth_bp
app.register_blueprint(auth_bp)
from blueprints.cards import cards_bp
app.register_blueprint(cards_bp)
from blueprints.admin.admin import admin_bp
app.register_blueprint(admin_bp)
from blueprints.main import main_bp
app.register_blueprint(main_bp)
csrf.exempt(app.view_functions['main.csp_report'])
# Error Handler für flask limiter - Anzeige sinnvoller Info an User
@app.errorhandler(429)
def ratelimit_handler(e):
"""
Handles a rate limit error (HTTP status code 429) by rendering a specific
template with a retry time. The function attempts to extract the retry
time from the exception description. If unsuccessful, it falls back to
a default retry time.
:param e: The exception object representing the rate limit error. It is
expected to have attributes such as `description` to parse the retry
time, if available.
:return: A rendered HTML template for the error page, including the retry
time, and an HTTP status code of 429.
"""
retry_after = 60 # fallback
if hasattr(e, "description") and "in" in str(e.description):
try:
retry_after = int(str(e.description).split("in")[-1].split(" ")[1])
except Exception:
pass
return render_template("rate_limited.html", retry_after=retry_after), 429
@app.errorhandler(413)
def too_large_handler(e):
"""
Handles uploads that exceed MAX_CONTENT_LENGTH with a readable message.
:param e: The exception raised by Werkzeug when the request body is too large.
:return: A flash-style message and HTTP status code 413.
"""
limit_mb = app.config.get("MAX_CONTENT_LENGTH", 0) // (1024 * 1024)
return (
render_template("expired.html", message=f"Die Datei ist zu groß (max. {limit_mb} MB)."),
413,
)
return app
def setup_logging(app):
"""
Configures logging for the given application based on its debug mode.
If the application is not in debug mode, it utilizes the Gunicorn logging
setup to configure the application logger. If the application is in debug
mode, a basic debug-level logging is configured.
Args:
app: The application object for which logging is to be configured.
Returns:
None
"""
if not app.debug:
gunicorn_logger = logging.getLogger('gunicorn.error')
app.logger.handlers = gunicorn_logger.handlers
app.logger.setLevel(gunicorn_logger.level)
else:
logging.basicConfig(level=logging.DEBUG)
if __name__ == "__main__":
app = create_app()
app.run(debug=True, host="127.0.0.1", port=5000)