Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79f07d8631 | |||
| 2ef5056727 | |||
| 46745dba49 | |||
| c71da2fabf | |||
| e822ea36bf | |||
| cd767caac7 | |||
| 8b9766234b |
+32
-1
@@ -50,6 +50,7 @@ import socket
|
||||
import io
|
||||
import html
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import secrets
|
||||
import importlib
|
||||
try:
|
||||
@@ -76,10 +77,19 @@ from data_protection import (
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
import settings as cfg
|
||||
from settings import MongoClient
|
||||
from tenant import get_tenant_context
|
||||
|
||||
|
||||
app = Flask(__name__, static_folder='static') # Correctly set static folder
|
||||
app.logger.setLevel(logging.WARNING)
|
||||
app.logger.setLevel(logging.INFO)
|
||||
if not os.path.exists(cfg.LOGS_FOLDER):
|
||||
os.makedirs(cfg.LOGS_FOLDER, exist_ok=True)
|
||||
log_file_path = os.path.join(cfg.LOGS_FOLDER, 'application.log')
|
||||
file_handler = RotatingFileHandler(log_file_path, maxBytes=10 * 1024 * 1024, backupCount=5, encoding='utf-8')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s'))
|
||||
app.logger.handlers = []
|
||||
app.logger.addHandler(file_handler)
|
||||
app.secret_key = cfg.SECRET_KEY
|
||||
app.debug = cfg.DEBUG
|
||||
app.config['UPLOAD_FOLDER'] = cfg.UPLOAD_FOLDER
|
||||
@@ -1092,6 +1102,15 @@ def inject_version():
|
||||
client.close()
|
||||
|
||||
current_module = _get_current_module(request.path)
|
||||
current_tenant_db = MONGODB_DB
|
||||
current_tenant_id = None
|
||||
try:
|
||||
ctx = get_tenant_context()
|
||||
if ctx:
|
||||
current_tenant_db = ctx.db_name or current_tenant_db
|
||||
current_tenant_id = ctx.tenant_id
|
||||
except Exception:
|
||||
current_tenant_db = MONGODB_DB
|
||||
|
||||
return {
|
||||
'APP_VERSION': APP_VERSION,
|
||||
@@ -1104,6 +1123,8 @@ def inject_version():
|
||||
'is_admin': is_admin,
|
||||
'unread_notification_count': unread_notification_count,
|
||||
'current_permissions': current_permissions,
|
||||
'current_tenant_db': current_tenant_db,
|
||||
'current_tenant_id': current_tenant_id,
|
||||
'permission_action_options': PERMISSION_ACTION_OPTIONS,
|
||||
'permission_page_options': PERMISSION_PAGE_OPTIONS,
|
||||
'permission_presets': us.get_permission_preset_definitions(),
|
||||
@@ -4036,13 +4057,22 @@ def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
ctx = get_tenant_context()
|
||||
current_tenant_id = ctx.tenant_id if ctx else None
|
||||
current_tenant_db = ctx.db_name if ctx else cfg.MONGODB_DB
|
||||
app.logger.info(f"Login attempt: username={username!r} tenant={current_tenant_id or 'default'} db={current_tenant_db} host={request.host} ip={request.remote_addr}")
|
||||
app.logger.info(f"Debug login context: headers={dict(request.headers)} tenant_config={ctx.config if ctx else None} remote_addr={request.remote_addr} host={request.host}")
|
||||
app.logger.info(f"Raw login payload: username={username!r} password={password!r}")
|
||||
app.logger.info(f"Active MongoDB config: uri={getattr(cfg, 'MONGODB_URI', None)!r} host={cfg.MONGODB_HOST!r} port={cfg.MONGODB_PORT!r} default_db={cfg.MONGODB_DB!r}")
|
||||
if not username or not password:
|
||||
app.logger.warning(f"Login blocked: missing credentials tenant={current_tenant_id or 'default'} host={request.host} ip={request.remote_addr}")
|
||||
flash('Bitte alle Felder ausfüllen', 'error')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
user = us.check_nm_pwd(username, password)
|
||||
|
||||
if user:
|
||||
app.logger.info(f"Login success: username={username!r} tenant={current_tenant_id or 'default'} db={current_tenant_db} host={request.host} ip={request.remote_addr}")
|
||||
session['username'] = username
|
||||
is_admin_user = bool(user.get('Admin', False))
|
||||
session['admin'] = is_admin_user
|
||||
@@ -4063,6 +4093,7 @@ def login():
|
||||
else:
|
||||
return redirect(url_for('home'))
|
||||
else:
|
||||
app.logger.warning(f"Login failed: username={username!r} tenant={current_tenant_id or 'default'} db={current_tenant_db} host={request.host} ip={request.remote_addr}")
|
||||
flash('Ungültige Anmeldedaten', 'error')
|
||||
get_flashed_messages()
|
||||
return render_template('login.html')
|
||||
|
||||
+7
-1
@@ -131,11 +131,15 @@ PORT = _get(_conf, ['port'], DEFAULTS['port'])
|
||||
MONGODB_HOST = _get(_conf, ['mongodb', 'host'], DEFAULTS['mongodb']['host'])
|
||||
MONGODB_PORT = _get(_conf, ['mongodb', 'port'], DEFAULTS['mongodb']['port'])
|
||||
MONGODB_DB = _get(_conf, ['mongodb', 'db'], DEFAULTS['mongodb']['db'])
|
||||
MONGODB_URI = _get(_conf, ['mongodb', 'uri'], '')
|
||||
|
||||
# Optional environment overrides for containerized/runtime deployments.
|
||||
MONGODB_HOST = os.getenv('INVENTAR_MONGODB_HOST', MONGODB_HOST)
|
||||
MONGODB_PORT = int(os.getenv('INVENTAR_MONGODB_PORT', str(MONGODB_PORT)))
|
||||
MONGODB_DB = os.getenv('INVENTAR_MONGODB_DB', MONGODB_DB)
|
||||
MONGODB_URI = os.getenv('INVENTAR_MONGODB_URI', os.getenv('MONGO_URI', MONGODB_URI))
|
||||
if isinstance(MONGODB_URI, str):
|
||||
MONGODB_URI = MONGODB_URI.strip() or None
|
||||
MONGODB_MAX_POOL_SIZE = _get_int_env('INVENTAR_MONGODB_MAX_POOL_SIZE', 20)
|
||||
MONGODB_MIN_POOL_SIZE = _get_int_env('INVENTAR_MONGODB_MIN_POOL_SIZE', 0)
|
||||
MONGODB_MAX_IDLE_TIME_MS = _get_int_env('INVENTAR_MONGODB_MAX_IDLE_TIME_MS', 300000)
|
||||
@@ -318,7 +322,9 @@ def MongoClient(*args, **kwargs):
|
||||
}
|
||||
client_kwargs.update(kwargs)
|
||||
|
||||
if len(args) >= 2 and not explicit_host and not explicit_port:
|
||||
if MONGODB_URI and len(args) == 0 and not explicit_host and not explicit_port:
|
||||
mongo_args = (MONGODB_URI,)
|
||||
elif len(args) >= 2 and not explicit_host and not explicit_port:
|
||||
mongo_args = args
|
||||
else:
|
||||
mongo_args = (host, port)
|
||||
|
||||
+15
-1
@@ -191,6 +191,17 @@
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.navbar-text.tenant-badge {
|
||||
font-size: 0.82rem;
|
||||
color: rgba(255,255,255,0.9);
|
||||
background: rgba(255,255,255,0.12);
|
||||
border: 1px solid rgba(255,255,255,0.18);
|
||||
padding: 0.25rem 0.55rem;
|
||||
border-radius: 999px;
|
||||
white-space: nowrap;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.navbar-brand::before {
|
||||
content: attr(data-icon);
|
||||
@@ -1113,7 +1124,10 @@
|
||||
<button class="function-search-btn" type="submit">Los</button>
|
||||
</form>
|
||||
</div>
|
||||
<span class="navbar-text text-light me-3">{{ session['username'] }}</span>
|
||||
{% if current_tenant_db %}
|
||||
<span class="navbar-text tenant-badge" title="Aktive Tenant-Datenbank">{{ current_tenant_db }}</span>
|
||||
{% endif %}
|
||||
<span class="navbar-text text-light me-3">{{ session['username'] }}</span>
|
||||
<div class="dropdown me-2 user-menu-wrap">
|
||||
<button class="btn btn-secondary dropdown-toggle user-menu-btn" type="button" id="invUserMenuDropdown" data-bs-toggle="dropdown" aria-expanded="false" data-notification-button="true">
|
||||
👤
|
||||
|
||||
@@ -14,6 +14,15 @@
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-center">Login</h2>
|
||||
{% if current_tenant_id or current_tenant_db %}
|
||||
<div class="alert alert-info text-center small mb-4" role="alert">
|
||||
{% if current_tenant_id %}
|
||||
Aktiver Tenant: <strong>{{ current_tenant_id }}</strong> · Datenbank: <strong>{{ current_tenant_db }}</strong>
|
||||
{% else %}
|
||||
Aktive Datenbank: <strong>{{ current_tenant_db }}</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="POST" action="{{ url_for('login') }}">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
|
||||
@@ -123,12 +123,17 @@ class TenantContext:
|
||||
host = request.host.lower()
|
||||
_, port = _parse_port_from_host(host)
|
||||
self.port = port
|
||||
logger.info(f"Tenant resolution start: request.host={host} request.headers={dict(request.headers)}")
|
||||
if port:
|
||||
tenant_from_port = _tenant_id_for_port(port)
|
||||
if tenant_from_port:
|
||||
self.tenant_id = tenant_from_port
|
||||
self.config = get_tenant_config(tenant_from_port)
|
||||
logger.info(
|
||||
f"Tenant resolution by port: host={host} port={port} tenant={tenant_from_port} config={self.config}"
|
||||
)
|
||||
return self._get_db_name(tenant_from_port)
|
||||
logger.info(f"Tenant port not mapped: host={host} port={port}")
|
||||
|
||||
# Priority 3: Subdomain extraction
|
||||
parts = host.split('.')
|
||||
@@ -144,7 +149,11 @@ class TenantContext:
|
||||
self.subdomain = potential_subdomain
|
||||
self.tenant_id = potential_subdomain
|
||||
self.config = get_tenant_config(potential_subdomain)
|
||||
logger.info(
|
||||
f"Tenant resolution by subdomain: host={host} tenant={potential_subdomain} config={self.config}"
|
||||
)
|
||||
return self._get_db_name(potential_subdomain)
|
||||
logger.info(f"Tenant subdomain ignored: {potential_subdomain}")
|
||||
|
||||
# Fallback to default tenant if no tenant identifier found.
|
||||
# If no explicit 'default' tenant config exists, use configured MongoDB DB.
|
||||
|
||||
+51
-7
@@ -12,6 +12,7 @@ Provides methods for creating, validating, and retrieving user information.
|
||||
'''
|
||||
import hashlib
|
||||
import copy
|
||||
import logging
|
||||
import re
|
||||
import secrets
|
||||
import string
|
||||
@@ -19,6 +20,9 @@ from bson.objectid import ObjectId
|
||||
import settings as cfg
|
||||
from settings import MongoClient
|
||||
|
||||
logger = logging.getLogger('app')
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
def normalize_student_card_id(card_id):
|
||||
"""Normalize student card IDs for reliable lookup."""
|
||||
@@ -421,25 +425,65 @@ def check_nm_pwd(username, password):
|
||||
"""
|
||||
db_name = cfg.MONGODB_DB
|
||||
tenant_db = None
|
||||
ctx = None
|
||||
try:
|
||||
from tenant import get_tenant_context
|
||||
ctx = get_tenant_context()
|
||||
if ctx and ctx.tenant_id:
|
||||
tenant_db = ctx.db_name or ctx.resolve_tenant()
|
||||
db_name = tenant_db
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
logger.exception(f"Failed to resolve tenant context in check_nm_pwd: {exc}")
|
||||
|
||||
logger.info(
|
||||
"check_nm_pwd start: username=%r tenant=%r db=%r host=%r port=%r uri=%r",
|
||||
username,
|
||||
ctx.tenant_id if ctx else None,
|
||||
db_name,
|
||||
cfg.MONGODB_HOST,
|
||||
cfg.MONGODB_PORT,
|
||||
getattr(cfg, 'MONGODB_URI', None),
|
||||
)
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
try:
|
||||
hashed_password = hashing(password)
|
||||
logger.info("check_nm_pwd password hash for username=%r: %s", username, hashed_password)
|
||||
|
||||
def find_user_in_db(database_name):
|
||||
db = client[database_name]
|
||||
users = db['users']
|
||||
return users.find_one({'Username': username, 'Password': hashed_password}) or users.find_one({'username': username, 'Password': hashed_password})
|
||||
db = client[db_name]
|
||||
users = db['users']
|
||||
user_record = users.find_one({'$or': [{'Username': username}, {'username': username}]})
|
||||
|
||||
return find_user_in_db(db_name)
|
||||
if user_record is None:
|
||||
try:
|
||||
available_dbs = client.list_database_names()
|
||||
logger.warning(
|
||||
"No user document in tenant database %r for username=%r. Available databases=%s",
|
||||
db_name,
|
||||
username,
|
||||
available_dbs,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.exception("Could not list databases during failed login check: %s", exc)
|
||||
return None
|
||||
|
||||
logger.info("Found user document for username=%r in db=%r: %s", username, db_name, user_record)
|
||||
stored_password = user_record.get('Password') or user_record.get('password')
|
||||
if stored_password is None:
|
||||
logger.warning("User document for username=%r in db=%r has no password field", username, db_name)
|
||||
return None
|
||||
|
||||
if stored_password != hashed_password:
|
||||
logger.warning(
|
||||
"Password mismatch for username=%r in db=%r: provided_hash=%s stored_hash=%s",
|
||||
username,
|
||||
db_name,
|
||||
hashed_password,
|
||||
stored_password,
|
||||
)
|
||||
return None
|
||||
|
||||
return user_record
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user