diff --git a/backend/eusrr_backend/settings.py b/backend/eusrr_backend/settings.py index 7f59b2ed..024da51d 100644 --- a/backend/eusrr_backend/settings.py +++ b/backend/eusrr_backend/settings.py @@ -99,36 +99,23 @@ ASGI_APPLICATION = "eusrr_backend.asgi.application" USE_SQLITE = os.getenv("USE_SQLITE", "false").lower() == "true" DATABASES = { - "default": {}, - + "default": { + "ENGINE": "django.db.backends.sqlite3" if USE_SQLITE else "django.db.backends.postgresql", + "NAME": os.path.join(BASE_DIR, "db.sqlite3") if USE_SQLITE else os.getenv("POSTGRES_DB", "django"), + } } -if USE_SQLITE: - DATABASES["default"] = { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "db.sqlite3"), - } -else: - DATABASES["default"] = { - "ENGINE": "django.db.backends.postgresql", - "NAME": os.getenv("POSTGRES_DB", "django"), +if not USE_SQLITE: + DATABASES["default"].update({ "USER": os.getenv("POSTGRES_USER", "django"), "PASSWORD": os.getenv("POSTGRES_PASSWORD", ""), "HOST": os.getenv("DB_HOST", "127.0.0.1"), "PORT": os.getenv("DB_PORT", "5432"), - } + }) # ----------------------------------------------------------------------------- # АВТОРИЗАЦИЯ / ПАРОЛИ # ----------------------------------------------------------------------------- -# AUTH_PASSWORD_VALIDATORS = [ -# { -# "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" -# }, -# {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, -# {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, -# {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, -# ] LANGUAGE_CODE = "ru" TIME_ZONE = "Europe/Moscow" @@ -184,35 +171,222 @@ FILE_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB # ----------------------------------------------------------------------------- # ЛОГИРОВАНИЕ # ----------------------------------------------------------------------------- +# Создаем директорию для логов +LOGS_DIR = BASE_DIR / "logs" +LOGS_DIR.mkdir(exist_ok=True) + LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { + #Detalized format for development "verbose": { - "format": "[{levelname}] {asctime} {name}:{lineno} {message}", + "format": "[{levelname:8s}] {asctime} | {name:30s} | {funcName:20s}:{lineno:4d} | {message}", "style": "{", + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + # Simple format for production console + "simple": { + "format": "[{levelname:8s}] {asctime} | {name} | {message}", + "style": "{", + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + # Structured format for parsing + "json_style": { + "format": "%(asctime)s | %(levelname)s | %(name)s | %(funcName)s:%(lineno)d | %(message)s", + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + }, + "filters": { + "require_debug_false": { + "()": "django.utils.log.RequireDebugFalse", + }, + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue", }, }, "handlers": { + # Console output "console": { - "level": "DEBUG", + "level": "DEBUG" if DEBUG else "INFO", "class": "logging.StreamHandler", + "formatter": "verbose" if DEBUG else "simple", + }, + # All logs (INFO+) with rotation + "file_all": { + "level": "INFO", + "class": "logging.handlers.RotatingFileHandler", + "filename": LOGS_DIR / "all.log", + "maxBytes": 10 * 1024 * 1024, # 10 MB + "backupCount": 5, + "formatter": "json_style", + "encoding": "utf-8", + }, + # Error logs (ERROR+) with rotation + "file_error": { + "level": "ERROR", + "class": "logging.handlers.RotatingFileHandler", + "filename": LOGS_DIR / "error.log", + "maxBytes": 10 * 1024 * 1024, # 10 MB + "backupCount": 10, + "formatter": "verbose", + "encoding": "utf-8", + }, + # Debug logs (only in DEBUG mode) + "file_debug": { + "level": "DEBUG", + "class": "logging.handlers.RotatingFileHandler", + "filename": LOGS_DIR / "debug.log", + "maxBytes": 20 * 1024 * 1024, # 20 MB + "backupCount": 3, + "formatter": "verbose", + "encoding": "utf-8", + "filters": ["require_debug_true"], + }, + # Security logs (authentication, permissions, etc) + "file_security": { + "level": "INFO", + "class": "logging.handlers.RotatingFileHandler", + "filename": LOGS_DIR / "security.log", + "maxBytes": 10 * 1024 * 1024, # 10 MB + "backupCount": 20, + "formatter": "json_style", + "encoding": "utf-8", + }, + # Django request logs + "file_requests": { + "level": "INFO", + "class": "logging.handlers.RotatingFileHandler", + "filename": LOGS_DIR / "requests.log", + "maxBytes": 20 * 1024 * 1024, # 20 MB + "backupCount": 5, + "formatter": "json_style", + "encoding": "utf-8", + }, + # Mail admins on errors (production only) + "mail_admins": { + "level": "ERROR", + "class": "django.utils.log.AdminEmailHandler", + "filters": ["require_debug_false"], "formatter": "verbose", }, }, "loggers": { - "documents": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, - "bots": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, - "communications": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, - "api.v1.communications": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, - "notifications": {"handlers": ["console"], "level": "INFO", "propagate": False}, - "requests_app": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, - "employees": {"handlers": ["console"], "level": "INFO", "propagate": False}, - "common": {"handlers": ["console"], "level": "INFO", "propagate": False}, - "django": {"handlers": ["console"], "level": "INFO", "propagate": False}, - "aiogram": {"handlers": ["console"], "level": "INFO", "propagate": False}, - # полезно видеть ошибки и отладку ldap3 - "ldap3": {"handlers": ["console"], "level": "WARNING", "propagate": False}, + # Root logger + "": { + "handlers": ["console", "file_all", "file_error"], + "level": "DEBUG" if DEBUG else "INFO", + }, + # Django core + "django": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, + "django.request": { + "handlers": ["console", "file_requests", "file_error", "mail_admins"], + "level": "WARNING", + "propagate": False, + }, + "django.server": { + "handlers": ["console", "file_requests"], + "level": "INFO", + "propagate": False, + }, + # SQL queries (only in DEBUG mode) + "django.db.backends": { + "handlers": ["console", "file_debug"] if DEBUG else [], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, + # Security events + "django.security": { + "handlers": ["console", "file_security", "mail_admins"], + "level": "WARNING", + "propagate": False, + }, + # Application loggers + "employees": { + "handlers": ["console", "file_all", "file_debug", "file_error"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, + "employees.ldap": { + "handlers": ["console", "file_all", "file_security", "file_error"], + "level": "INFO", + "propagate": False, + }, + "documents": { + "handlers": ["console", "file_all", "file_debug", "file_error"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, + "communications": { + "handlers": ["console", "file_all", "file_debug", "file_error"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, + "api.v1.communications": { + "handlers": ["console", "file_all", "file_debug", "file_error"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, + "notifications": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, + "requests_app": { + "handlers": ["console", "file_all", "file_debug", "file_error"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, + "bots": { + "handlers": ["console", "file_all", "file_debug", "file_error"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, + "feed": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, + "calendar_app": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, + "finance": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, + "common": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, + # Third-party integrations + "ldap3": { + "handlers": ["console", "file_security", "file_error"], + "level": "WARNING", + "propagate": False, + }, + "aiogram": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, + "celery": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, + "channels": { + "handlers": ["console", "file_all", "file_error"], + "level": "INFO", + "propagate": False, + }, }, } @@ -334,79 +508,78 @@ AUTHENTICATION_BACKENDS = [ # ----------------------------------------------------------------------------- # LDAP (двусторонний обмен + LWW) # ----------------------------------------------------------------------------- -LDAP_ENABLED = os.getenv("LDAP_ENABLED", "true").lower() == "true" +LDAP_ENABLED = os.getenv("LDAP_ENABLED", "false").lower() == "true" # Основное подключение -LDAP_URI = os.getenv("LDAP_URI", "ldaps://dcii.robotail.local:636") +LDAP_URI = os.getenv("LDAP_URI", "ldap://127.0.0.1:389") LDAP_BIND_DN = os.getenv("LDAP_BIND_DN", "") LDAP_BIND_PASSWORD = os.getenv("LDAP_BIND_PASSWORD", "") # TLS/CA -LDAP_CA_CERTS = os.getenv("LDAP_CA_CERTS", "") # путь к CA bundle/серту (если нужен) -LDAP_TLS_REQUIRED = os.getenv("LDAP_TLS_REQUIRED", "true").lower() == "true" +LDAP_CA_CERTS = os.getenv("LDAP_CA_CERTS", "") +LDAP_TLS_REQUIRED = os.getenv("LDAP_TLS_REQUIRED", "false").lower() == "true" -# Где искать пользователей -LDAP_USER_BASE = os.getenv("LDAP_USER_BASE", "OU=company,DC=robotail,DC=local") -# Где создавать новых пользователей (может отличаться от базы поиска) +# DN базы пользователей +LDAP_USER_BASE = os.getenv("LDAP_USER_BASE", "ou=users,dc=eusrr,dc=local") LDAP_USERS_BASE = os.getenv("LDAP_USERS_BASE", LDAP_USER_BASE) -# Базовый DN для операций создания (если не указан department_dn) LDAP_BASE_DN = os.getenv("LDAP_BASE_DN", LDAP_USERS_BASE) -# UPN-суффикс для создания пользователей (userPrincipalName) - домен БЕЗ @ -LDAP_UPN_SUFFIX = os.getenv("LDAP_USER_UPN_SUFFIX", "robotail.local") +# UPN суффикс (домен БЕЗ @) +LDAP_UPN_SUFFIX = os.getenv("LDAP_USER_UPN_SUFFIX", "eusrr.local") + +# Фильтры поиска (для OpenLDAP по умолчанию) LDAP_USER_FILTER = os.getenv( - "LDAP_USER_FILTER", "(&(objectCategory=person)(objectClass=user))" -) -# Для AD по умолчанию; для OpenLDAP можно обнулить в .env -LDAP_ACTIVE_FILTER = os.getenv( - "LDAP_ACTIVE_FILTER", - "(!(userAccountControl:1.2.840.113556.1.4.803:=2))", # битовый NOT DISABLED + "LDAP_USER_FILTER", "(&(objectClass=inetOrgPerson))" ) +LDAP_ACTIVE_FILTER = os.getenv("LDAP_ACTIVE_FILTER", "") +# Атрибуты LDAP LDAP_ATTR_MAIL = os.getenv("LDAP_ATTR_MAIL", "mail") LDAP_ATTR_GIVENNAME = os.getenv("LDAP_ATTR_GIVENNAME", "givenName") LDAP_ATTR_SN = os.getenv("LDAP_ATTR_SN", "sn") LDAP_ATTR_PHONE = os.getenv("LDAP_ATTR_PHONE", "telephoneNumber") -# Атрибут LDAP для хранения Django pk сотрудника (employeeNumber по RFC 2798, employeeID для AD) LDAP_EMPLOYEE_ID_ATTR = os.getenv("LDAP_EMPLOYEE_ID_ATTR", "employeeNumber") LDAP_PHONE_ATTRS = tuple( _split_env_list(os.getenv("LDAP_PHONE_ATTRS", "mobile,telephoneNumber")) ) -LDAP_DEPT_ATTR = os.getenv("LDAP_DEPT_ATTR", "") # например, departmentNumber +# Отделы и группы +LDAP_DEPT_ATTR = os.getenv("LDAP_DEPT_ATTR", "") +LDAP_DEPARTMENTS_BASE = os.getenv( + "LDAP_DEPARTMENTS_BASE", "OU=Departments,OU=company,DC=eusrr,DC=local" +) +LDAP_DISMISSED_BASE = os.getenv( + "LDAP_DISMISSED_BASE", "OU=Dismissed,OU=company,DC=eusrr,DC=local" +) +LDAP_POSITIONS_BASE = os.getenv( + "LDAP_POSITIONS_BASE", "OU=Positions,OU=company,DC=eusrr,DC=local" +) +LDAP_GROUPS_BASE = os.getenv("LDAP_GROUPS_BASE", "") + LDAP_SYNC_GROUPS = os.getenv("LDAP_SYNC_GROUPS", "false").lower() == "true" LDAP_GROUP_ATTR = os.getenv("LDAP_GROUP_ATTR", "memberOf") -# Пример: {"CN=HR,OU=Groups,DC=...,DC=...": "hr"} LDAP_GROUP_MAP = {} LDAP_GROUPS_EXCLUSIVE = os.getenv("LDAP_GROUPS_EXCLUSIVE", "false").lower() == "true" -# WRITE-BACK +# WRITE-BACK (двусторонняя синхронизация) LDAP_WRITE_ENABLED = os.getenv("LDAP_WRITE_ENABLED", "false").lower() == "true" LDAP_WRITE_DN = os.getenv("LDAP_WRITE_DN", LDAP_BIND_DN) LDAP_WRITE_PASSWORD = os.getenv("LDAP_WRITE_PASSWORD", LDAP_BIND_PASSWORD) LDAP_WRITE_TIMEOUT = int(os.getenv("LDAP_WRITE_TIMEOUT", "5")) -# UPN суффикс для создания пользователей (домен БЕЗ @, например robotail.local) -LDAP_UPN_SUFFIX = os.getenv("LDAP_USER_UPN_SUFFIX", "robotail.local") - -# Белый список: локальные поля -> LDAP-атрибуты +# Маппинг локальных полей на LDAP атрибуты LDAP_WRITE_ATTRS = { "first_name": LDAP_ATTR_GIVENNAME, "last_name": LDAP_ATTR_SN, - # фактическое имя локального телефонного поля подставляет код "phone": LDAP_ATTR_PHONE, - # "photo": "jpegPhoto", } -# LWW (Last Writer Wins) — сравнение меток изменения -# Для AD обычно whenChanged, для OpenLDAP — modifyTimestamp -LDAP_ASSERT_ATTR_AD = "whenChanged" -LDAP_ASSERT_ATTR_OL = "modifyTimestamp" -LDAP_ASSERT_ATTR = os.getenv("LDAP_ASSERT_ATTR", LDAP_ASSERT_ATTR_AD) - -# Локальное поле «последнее изменение» (ваше поле модели, напр. updated_at) +# LWW (Last Writer Wins) для разрешения конфликтов синхронизации +LDAP_ASSERT_ATTR = os.getenv("LDAP_ASSERT_ATTR", "modifyTimestamp") # modifyTimestamp (OpenLDAP) или whenChanged (AD) LOCAL_ASSERT_FIELD = os.getenv("LOCAL_ASSERT_FIELD", "updated_at") + +# Дополнительные атрибуты при создании пользователя в LDAP LDAP_CREATE_EXTRA_ATTRS = { "sAMAccountName": "{username20}", "userPrincipalName": "{upn}", @@ -415,29 +588,18 @@ LDAP_CREATE_EXTRA_ATTRS = { "displayName": "{cn}", "mail": "{email}", } + +# Режим синхронизации и таймауты LDAP_SYNC_MODE = os.getenv("LDAP_SYNC_MODE", "lww") # lww|ldap|django LDAP_PURGE = os.getenv("LDAP_PURGE", "false").lower() == "true" - LDAP_CONNECT_TIMEOUT = int(os.getenv("LDAP_CONNECT_TIMEOUT", "5")) LDAP_OPERATION_TIMEOUT = int(os.getenv("LDAP_OPERATION_TIMEOUT", "10")) - -LDAP_USERS_BASE = os.getenv("LDAP_USERS_BASE") -LDAP_USER_BASE = os.getenv("LDAP_USER_BASE") -LDAP_DEPARTMENTS_BASE = os.getenv( - "LDAP_DEPARTMENTS_BASE", "OU=Departments,OU=company,DC=robotail,DC=local" -) -LDAP_DISMISSED_BASE = os.getenv( - "LDAP_DISMISSED_BASE", "OU=Dismissed,OU=company,DC=robotail,DC=local" -) -LDAP_GROUPS_BASE = os.getenv("LDAP_GROUPS_BASE") - -LDAP_AUTO_CREATE = os.getenv("LDAP_AUTO_CREATE", "False") - +# Флаги поведения +LDAP_AUTO_CREATE = os.getenv("LDAP_AUTO_CREATE", "False").lower() == "true" LDAP_RESPECT_IS_ACTIVE = True LDAP_RESPECT_AD_DISABLED = True LDAP_REGISTRATION_CREATE = True -LDAP_POSITIONS_BASE = os.getenv("LDAP_POSITIONS_BASE", "OU=Positions,OU=company,DC=robotail,DC=local")