mirror of
https://github.com/hay-kot/mealie.git
synced 2025-07-16 10:03:54 -07:00
* Use Base DN for LDAP and fetch user attrs Requires that a Base DN be set for LDAP Set `full_name` and `email` based on LDAP attributes when creating user * Add support for secure LDAP Allow insecure LDAP connection (disabled by default) Use CA when connecting to secure LDAP server * Added missing quotes to example * Update security.py * Update security.py formatting * Update security.py Switched to f-String formatting * formatting * Update test_security.py Added at attributes for testing * Update test_security.py Modified tests for base DN * Update test_security.py Set proper base DN for testing * Update test_security.py Corrected testing for LDAP * Update test_security.py Defined base_dn * Authenticated user not in base DN Add check for when user can authenticate but is not in base DN * Update test_security.py LDAP user cannot exist as it is searched before it is created and the list returns False Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
164 lines
4.7 KiB
Python
164 lines
4.7 KiB
Python
import secrets
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseSettings, NoneStr
|
|
|
|
from .db_providers import AbstractDBProvider, db_provider_factory
|
|
|
|
|
|
def determine_secrets(data_dir: Path, production: bool) -> str:
|
|
if not production:
|
|
return "shh-secret-test-key"
|
|
|
|
secrets_file = data_dir.joinpath(".secret")
|
|
if secrets_file.is_file():
|
|
with open(secrets_file, "r") as f:
|
|
return f.read()
|
|
else:
|
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
with open(secrets_file, "w") as f:
|
|
new_secret = secrets.token_hex(32)
|
|
f.write(new_secret)
|
|
return new_secret
|
|
|
|
|
|
class AppSettings(BaseSettings):
|
|
PRODUCTION: bool
|
|
BASE_URL: str = "http://localhost:8080"
|
|
IS_DEMO: bool = False
|
|
API_PORT: int = 9000
|
|
API_DOCS: bool = True
|
|
TOKEN_TIME: int = 48 # Time in Hours
|
|
SECRET: str
|
|
|
|
GIT_COMMIT_HASH: str = "unknown"
|
|
|
|
ALLOW_SIGNUP: bool = True
|
|
|
|
# ===============================================
|
|
# Security Configuration
|
|
|
|
SECURITY_MAX_LOGIN_ATTEMPTS: int = 5
|
|
SECURITY_USER_LOCKOUT_TIME: int = 24 # Time in Hours
|
|
|
|
@property
|
|
def DOCS_URL(self) -> str | None:
|
|
return "/docs" if self.API_DOCS else None
|
|
|
|
@property
|
|
def REDOC_URL(self) -> str | None:
|
|
return "/redoc" if self.API_DOCS else None
|
|
|
|
# ===============================================
|
|
# Database Configuration
|
|
|
|
DB_ENGINE: str = "sqlite" # Options: 'sqlite', 'postgres'
|
|
DB_PROVIDER: Optional[AbstractDBProvider] = None
|
|
|
|
@property
|
|
def DB_URL(self) -> str | None:
|
|
return self.DB_PROVIDER.db_url if self.DB_PROVIDER else None
|
|
|
|
@property
|
|
def DB_URL_PUBLIC(self) -> str | None:
|
|
return self.DB_PROVIDER.db_url_public if self.DB_PROVIDER else None
|
|
|
|
DEFAULT_GROUP: str = "Home"
|
|
DEFAULT_EMAIL: str = "changeme@email.com"
|
|
DEFAULT_PASSWORD: str = "MyPassword"
|
|
|
|
# ===============================================
|
|
# Email Configuration
|
|
|
|
SMTP_HOST: Optional[str]
|
|
SMTP_PORT: Optional[str] = "587"
|
|
SMTP_FROM_NAME: Optional[str] = "Mealie"
|
|
SMTP_FROM_EMAIL: Optional[str]
|
|
SMTP_USER: Optional[str]
|
|
SMTP_PASSWORD: Optional[str]
|
|
SMTP_AUTH_STRATEGY: Optional[str] = "TLS" # Options: 'TLS', 'SSL', 'NONE'
|
|
|
|
@property
|
|
def SMTP_ENABLE(self) -> bool:
|
|
return AppSettings.validate_smtp(
|
|
self.SMTP_HOST,
|
|
self.SMTP_PORT,
|
|
self.SMTP_FROM_NAME,
|
|
self.SMTP_FROM_EMAIL,
|
|
self.SMTP_AUTH_STRATEGY,
|
|
self.SMTP_USER,
|
|
self.SMTP_PASSWORD,
|
|
)
|
|
|
|
@staticmethod
|
|
def validate_smtp(
|
|
host: str,
|
|
port: str,
|
|
from_name: str,
|
|
from_email: str,
|
|
strategy: str,
|
|
user: str | None = None,
|
|
password: str | None = None,
|
|
) -> bool:
|
|
"""Validates all SMTP variables are set"""
|
|
required = {host, port, from_name, from_email, strategy}
|
|
|
|
if strategy.upper() in {"TLS", "SSL"}:
|
|
required.add(user)
|
|
required.add(password)
|
|
|
|
return "" not in required and None not in required
|
|
|
|
# ===============================================
|
|
# LDAP Configuration
|
|
|
|
LDAP_AUTH_ENABLED: bool = False
|
|
LDAP_SERVER_URL: NoneStr = None
|
|
LDAP_TLS_INSECURE: bool = False
|
|
LDAP_TLS_CACERTFILE: NoneStr = None
|
|
LDAP_BIND_TEMPLATE: NoneStr = None
|
|
LDAP_BASE_DN: NoneStr = None
|
|
LDAP_ADMIN_FILTER: NoneStr = None
|
|
|
|
@property
|
|
def LDAP_ENABLED(self) -> bool:
|
|
"""Validates LDAP settings are all set"""
|
|
required = {
|
|
self.LDAP_SERVER_URL,
|
|
self.LDAP_BIND_TEMPLATE,
|
|
self.LDAP_BASE_DN,
|
|
self.LDAP_ADMIN_FILTER,
|
|
}
|
|
not_none = None not in required
|
|
return self.LDAP_AUTH_ENABLED and not_none
|
|
|
|
# ===============================================
|
|
# Testing Config
|
|
|
|
TESTING: bool = False
|
|
|
|
class Config:
|
|
arbitrary_types_allowed = True
|
|
|
|
|
|
def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, env_encoding="utf-8") -> AppSettings:
|
|
"""
|
|
app_settings_constructor is a factory function that returns an AppSettings object. It is used to inject the
|
|
required dependencies into the AppSettings object and nested child objects. AppSettings should not be substantiated
|
|
directly, but rather through this factory function.
|
|
"""
|
|
app_settings = AppSettings(
|
|
_env_file=env_file, # type: ignore
|
|
_env_file_encoding=env_encoding, # type: ignore
|
|
**{"SECRET": determine_secrets(data_dir, production)},
|
|
)
|
|
|
|
app_settings.DB_PROVIDER = db_provider_factory(
|
|
app_settings.DB_ENGINE or "sqlite",
|
|
data_dir,
|
|
env_file=env_file,
|
|
env_encoding=env_encoding,
|
|
)
|
|
|
|
return app_settings
|