Files
office365manage/office365_admin/settings.py

132 lines
4.6 KiB
Python

from __future__ import annotations
import os
from dataclasses import dataclass, field
from dotenv import load_dotenv
GRAPH_BASE_URL = "https://graph.microsoft.com/v1.0"
GRAPH_SCOPE = "https://graph.microsoft.com/.default"
TOKEN_ENDPOINT_TEMPLATE = "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
def _env_bool(name: str, default: bool = False) -> bool:
return os.getenv(name, str(default)).strip().lower() in {"1", "true", "yes", "on"}
def _env_int(name: str, default: int) -> int:
try:
return int(os.getenv(name, str(default)).strip())
except ValueError:
return default
@dataclass
class Settings:
app_name: str
host: str
port: int
debug: bool
session_secret: str
auth_enabled: bool
admin_username: str
admin_password: str
client_id: str
tenant_id: str
client_secret: str
default_password: str
default_domain: str
default_usage_location: str
default_license_sku: str
force_change_password: bool
graph_base_url: str
token_endpoint: str
scope: str
default_page_size: int = 25
max_page_size: int = 100
validation_errors: tuple[str, ...] = field(default_factory=tuple)
warnings: tuple[str, ...] = field(default_factory=tuple)
@property
def graph_ready(self) -> bool:
return not self.validation_errors
@property
def effective_auth_enabled(self) -> bool:
return self.auth_enabled and bool(self.admin_username and self.admin_password)
def to_public_dict(self) -> dict:
return {
"appName": self.app_name,
"graphFlavor": "Microsoft Graph Global",
"graphReady": self.graph_ready,
"validationErrors": list(self.validation_errors),
"warnings": list(self.warnings),
"authEnabled": self.effective_auth_enabled,
"defaultDomain": self.default_domain,
"defaultUsageLocation": self.default_usage_location,
"defaultLicenseSku": self.default_license_sku,
"forceChangePassword": self.force_change_password,
"pageSize": self.default_page_size,
"maxPageSize": self.max_page_size,
}
def load_settings() -> Settings:
load_dotenv()
tenant_id = os.getenv("TENANT_ID", "").strip()
graph_base_url = GRAPH_BASE_URL
token_endpoint = TOKEN_ENDPOINT_TEMPLATE.format(tenant_id=tenant_id) if tenant_id else ""
scope = GRAPH_SCOPE
validation_errors: list[str] = []
warnings: list[str] = []
required_fields = {
"CLIENT_ID": os.getenv("CLIENT_ID", "").strip(),
"TENANT_ID": tenant_id,
"CLIENT_SECRET": os.getenv("CLIENT_SECRET", "").strip(),
"DEFAULT_PASSWORD": os.getenv("DEFAULT_PASSWORD", "").strip(),
}
for field_name, value in required_fields.items():
if not value:
validation_errors.append(f"{field_name} 未配置")
if not os.getenv("DEFAULT_DOMAIN", "").strip():
warnings.append("DEFAULT_DOMAIN 未配置,创建账号时必须填写完整 userPrincipalName。")
auth_enabled = _env_bool("WEB_AUTH_ENABLED", True)
admin_username = os.getenv("ADMIN_USERNAME", "").strip()
admin_password = os.getenv("ADMIN_PASSWORD", "").strip()
if auth_enabled and not (admin_username and admin_password):
warnings.append("WEB_AUTH_ENABLED=true 但未配置后台登录账号,已自动退回为无登录保护模式。")
return Settings(
app_name=os.getenv("APP_NAME", "Office 365 User Management Platform").strip(),
host=os.getenv("HOST", "0.0.0.0").strip(),
port=_env_int("PORT", 8000),
debug=_env_bool("DEBUG", False),
session_secret=os.getenv("SESSION_SECRET", "office365-admin-dev-secret").strip(),
auth_enabled=auth_enabled,
admin_username=admin_username,
admin_password=admin_password,
client_id=required_fields["CLIENT_ID"],
tenant_id=required_fields["TENANT_ID"],
client_secret=required_fields["CLIENT_SECRET"],
default_password=required_fields["DEFAULT_PASSWORD"],
default_domain=os.getenv("DEFAULT_DOMAIN", "").strip(),
default_usage_location=os.getenv("DEFAULT_USAGE_LOCATION", "US").strip() or "US",
default_license_sku=os.getenv("DEFAULT_LICENSE_SKU", "").strip(),
force_change_password=_env_bool("FORCE_CHANGE_PASSWORD", True),
graph_base_url=graph_base_url,
token_endpoint=token_endpoint,
scope=scope,
default_page_size=min(max(_env_int("DEFAULT_PAGE_SIZE", 25), 1), 100),
max_page_size=min(max(_env_int("MAX_PAGE_SIZE", 100), 10), 500),
validation_errors=tuple(validation_errors),
warnings=tuple(warnings),
)