Files
linux-user-timer/app/users.py
T
2026-09-19 12:46:23 +05:00

174 lines
3.9 KiB
Python

import grp
import pwd
import subprocess
from typing import List, Dict
def linux_user_exists(username: str) -> bool:
try:
pwd.getpwnam(username)
return True
except KeyError:
return False
def get_uid(username: str) -> int:
return pwd.getpwnam(username).pw_uid
def _login_def_value(name: str, default: int) -> int:
"""Read an integer value from /etc/login.defs."""
try:
with open("/etc/login.defs", "r", encoding="utf-8") as handle:
for line in handle:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if len(parts) >= 2 and parts[0] == name:
value = int(parts[1])
if value > 0:
return value
except (OSError, ValueError):
pass
return default
def _uid_min() -> int:
return _login_def_value("UID_MIN", 1000)
def _uid_max() -> int:
return _login_def_value("UID_MAX", 60000)
def _is_interactive_shell(shell: str) -> bool:
"""Exclude service accounts that cannot be used for interactive logins."""
shell = (shell or "").strip().lower()
if not shell:
return False
return not shell.endswith(("/nologin", "/false"))
def _supplementary_groups(username: str) -> set[str]:
groups = set()
try:
user = pwd.getpwnam(username)
except KeyError:
return groups
try:
groups.add(grp.getgrgid(user.pw_gid).gr_name)
except KeyError:
pass
for group in grp.getgrall():
if username in group.gr_mem:
groups.add(group.gr_name)
return groups
def has_root_privileges(username: str) -> bool:
"""Best-effort detection for accounts with ordinary root-style group access."""
try:
user = pwd.getpwnam(username)
except KeyError:
return False
if user.pw_uid == 0:
return True
groups = _supplementary_groups(username)
return bool(groups.intersection({"root", "wheel", "sudo"}))
def is_non_root_user(username: str) -> bool:
"""Return True for regular non-root accounts suitable for parental control."""
try:
user = pwd.getpwnam(username)
except KeyError:
return False
if user.pw_uid < _uid_min() or user.pw_uid > _uid_max():
return False
if username in {"nobody", "nfsnobody"}:
return False
if not _is_interactive_shell(user.pw_shell):
return False
return not has_root_privileges(username)
def list_available_users() -> List[Dict[str, str]]:
"""Return regular interactive users that can be selected for parental control."""
users = []
for user in pwd.getpwall():
if not is_non_root_user(user.pw_name):
continue
users.append({"username": user.pw_name})
return sorted(users, key=lambda item: item["username"].lower())
def user_in_group(username: str, group_name: str) -> bool:
try:
group = grp.getgrnam(group_name)
user = pwd.getpwnam(username)
except KeyError:
return False
return (
username in group.gr_mem
or user.pw_gid == group.gr_gid
)
def is_locked(username: str) -> bool:
result = subprocess.run(
["passwd", "-S", username],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
return False
parts = result.stdout.split()
if len(parts) < 2:
return False
return parts[1] == "L"
def lock_user(username: str):
subprocess.run(
["loginctl", "terminate-user", username],
check=False,
)
subprocess.run(
["passwd", "-l", username],
check=True,
)
def unlock_user(username: str):
subprocess.run(
["passwd", "-u", username],
check=True,
)
def terminate_user(username: str):
subprocess.run(
["loginctl", "terminate-user", username],
check=False,
)