mirror of
https://github.com/wizarrrr/wizarr.git
synced 2026-07-30 23:07:19 -04:00
Security: - Fix XSS in HTMX responses by replacing f-string HTML with Jinja partials - Fix TLS verify_cert not being applied (add Tls config to ldap3 Server) - Fix password_hash NOT NULL conflict for LDAP-only admin accounts - Fix should_create_ldap_user defaulting to True for existing invitations Correctness: - Fix DN reconstruction fragility using escape_rdn for special characters - Fix connection leaks with try/finally in all LDAPClient methods - Fix sync interval logic (check FLASK_ENV not WIZARR_ENABLE_SCHEDULER) - Only register LDAP sync job when LDAP is actually configured - Make find_user_dn and service_connection public API methods Cleanup: - Squash 5 incremental LDAP migrations into single 20251226_add_ldap_support - Remove duplicate imports/queries in auth routes - Replace f-string logging with %s formatting - Extract inline HTML to Jinja template partials
109 lines
3.3 KiB
Python
109 lines
3.3 KiB
Python
"""LDAP user synchronization background task."""
|
|
|
|
import logging
|
|
import os
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _get_ldap_sync_interval():
|
|
"""Get the interval for LDAP sync based on environment.
|
|
|
|
Returns:
|
|
int: Interval in minutes for LDAP sync
|
|
"""
|
|
# Check environment variable first
|
|
env_interval = os.getenv("LDAP_SYNC_INTERVAL_MINUTES")
|
|
if env_interval:
|
|
try:
|
|
return int(env_interval)
|
|
except ValueError:
|
|
logger.warning(
|
|
"Invalid LDAP_SYNC_INTERVAL_MINUTES value: %s, using default",
|
|
env_interval,
|
|
)
|
|
|
|
# Use 15 minutes for development, 60 minutes (1 hour) for production
|
|
if os.getenv("FLASK_ENV") == "development":
|
|
return 15
|
|
return 60
|
|
|
|
|
|
def sync_ldap_users(app=None):
|
|
"""Automatically sync all users from LDAP server.
|
|
|
|
This task runs periodically to import new LDAP users into Wizarr.
|
|
Existing users are skipped, only new users are imported.
|
|
|
|
Args:
|
|
app: Flask application instance. If None, will try to get from current context.
|
|
"""
|
|
if app is None:
|
|
from flask import current_app
|
|
|
|
try:
|
|
app = current_app._get_current_object() # type: ignore
|
|
except RuntimeError:
|
|
logger.error(
|
|
"sync_ldap_users called outside application context and no app provided"
|
|
)
|
|
return
|
|
|
|
with app.app_context():
|
|
from app.models import LDAPConfiguration, User
|
|
from app.services.ldap.user_sync import import_ldap_user, list_ldap_users
|
|
|
|
# Check if LDAP is enabled
|
|
ldap_config = LDAPConfiguration.query.filter_by(enabled=True).first()
|
|
if not ldap_config:
|
|
# LDAP not configured, skip silently
|
|
return
|
|
|
|
# Get all LDAP users
|
|
success, result = list_ldap_users()
|
|
if not success:
|
|
logger.warning("LDAP sync failed: %s", result)
|
|
return
|
|
|
|
ldap_users = result
|
|
|
|
# Import each user that doesn't exist yet
|
|
imported_count = 0
|
|
skipped_count = 0
|
|
error_count = 0
|
|
|
|
for ldap_user in ldap_users:
|
|
username = ldap_user["username"]
|
|
|
|
# Check if user already exists
|
|
existing = User.query.filter_by(username=username).first()
|
|
if existing:
|
|
skipped_count += 1
|
|
continue
|
|
|
|
# Import the user
|
|
import_success, import_msg = import_ldap_user(username)
|
|
if import_success:
|
|
imported_count += 1
|
|
logger.info("LDAP sync: Imported user %s", username)
|
|
else:
|
|
error_count += 1
|
|
logger.warning(
|
|
"LDAP sync: Failed to import %s: %s", username, import_msg
|
|
)
|
|
|
|
# Log summary
|
|
if imported_count > 0:
|
|
logger.info(
|
|
"LDAP sync: Imported %d new user(s), skipped %d existing, %d error(s)",
|
|
imported_count,
|
|
skipped_count,
|
|
error_count,
|
|
)
|
|
elif os.getenv("FLASK_ENV") == "development":
|
|
# Only log in development mode to avoid spam
|
|
logger.debug(
|
|
"LDAP sync: No new users (checked %d LDAP users)",
|
|
len(ldap_users),
|
|
)
|