88 lines
3.8 KiB
Python

from pathlib import Path
import shutil
from typing import List, Optional
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
token: str = Field(alias="TOKEN")
server_name: str = Field(default="user-manage-api", alias="SERVER_NAME")
home_base_dir: str = Field(default="/home", alias="HOME_BASE_DIR")
link_home_dir: str = Field(default="", alias="LINK_HOME_DIR")
whitelist_users: str = Field(default="", alias="WHITELIST_USERS")
whitelist_groups: str = Field(default="", alias="WHITELIST_GROUPS")
locked_users: str = Field(default="", alias="LOCKED_USERS")
hidden_users: str = Field(default="", alias="HIDDEN_USERS")
hidden_groups: str = Field(default="", alias="HIDDEN_GROUPS")
user_uid_min: Optional[int] = Field(default=None, alias="USER_UID_MIN")
user_uid_max: Optional[int] = Field(default=None, alias="USER_UID_MAX")
group_gid_min: Optional[int] = Field(default=None, alias="GROUP_GID_MIN")
group_gid_max: Optional[int] = Field(default=None, alias="GROUP_GID_MAX")
use_libuser: bool = Field(default=False, alias="USE_LIBUSER")
log_level: str = Field(default="INFO", alias="LOG_LEVEL")
log_path: str = Field(default="./logs/user_manage_api.log", alias="LOG_PATH")
sudo_path: str = Field(default="/usr/bin/sudo", alias="SUDO_PATH")
command_timeout_seconds: int = 10
@field_validator("user_uid_min", "user_uid_max", "group_gid_min", "group_gid_max", mode="before")
@classmethod
def empty_string_to_none(cls, value: object) -> object:
if value == "":
return None
return value
@property
def whitelist_user_list(self) -> List[str]:
return self._parse_comma_separated_list(self.whitelist_users)
@property
def whitelist_group_list(self) -> List[str]:
return self._parse_comma_separated_list(self.whitelist_groups)
@property
def locked_user_list(self) -> List[str]:
return self._parse_comma_separated_list(self.locked_users)
@property
def hidden_user_list(self) -> List[str]:
return self._parse_comma_separated_list(self.hidden_users)
@property
def hidden_group_list(self) -> List[str]:
return self._parse_comma_separated_list(self.hidden_groups)
def _parse_comma_separated_list(self, value: str) -> List[str]:
return [item.strip() for item in value.split(",") if item.strip()]
def validate_settings(settings: Settings) -> None:
if not settings.token.strip():
raise ValueError("TOKEN is required and cannot be empty.")
log_parent = Path(settings.log_path).parent
log_parent.mkdir(parents=True, exist_ok=True)
if not log_parent.exists() or not log_parent.is_dir():
raise ValueError(f"LOG_PATH parent is invalid: {log_parent}")
if shutil.which(settings.sudo_path) is None and not Path(settings.sudo_path).exists():
raise ValueError(f"SUDO_PATH is not executable: {settings.sudo_path}")
if settings.user_uid_min is not None and settings.user_uid_max is not None and settings.user_uid_min > settings.user_uid_max:
raise ValueError("USER_UID_MIN cannot be greater than USER_UID_MAX.")
if settings.group_gid_min is not None and settings.group_gid_max is not None and settings.group_gid_min > settings.group_gid_max:
raise ValueError("GROUP_GID_MIN cannot be greater than GROUP_GID_MAX.")
required_commands = ["useradd", "usermod", "userdel", "groupadd", "groupdel", "chpasswd", "id", "getent"]
if settings.link_home_dir.strip():
required_commands.extend(["mkdir", "ln", "chown", "unlink"])
for command in required_commands:
if shutil.which(command) is None:
raise ValueError(f"Required command not found in PATH: {command}")