from __future__ import annotations import tomllib from typing import Any import tomli_w from fastapi import HTTPException from Settings import GetDataDirectory def GetLocksPath(): return GetDataDirectory() / "LockedDirectories.toml" def LoadLocks() -> list[dict[str, Any]]: LocksPath = GetLocksPath() if not LocksPath.is_file(): return [] with LocksPath.open("rb") as LocksFile: Loaded = tomllib.load(LocksFile) return Loaded.get("Locks", []) def SaveLocks(Locks: list[dict[str, Any]]) -> None: GetDataDirectory().mkdir(parents=True, exist_ok=True) with GetLocksPath().open("wb") as LocksFile: tomli_w.dump({"Locks": Locks}, LocksFile) def GetLockForPath(RootName: str, RelativePath: str) -> dict[str, Any] | None: for Lock in LoadLocks(): if Lock.get("Root") == RootName and Lock.get("Path") == RelativePath: return Lock return None def SetLock(RootName: str, RelativePath: str, Usernames: list[str], GroupNames: list[str]) -> None: if not RelativePath: raise ValueError("Can't lock an entire root - pick a specific file or directory.") if not Usernames and not GroupNames: RemoveLock(RootName, RelativePath) return Locks = LoadLocks() for Lock in Locks: if Lock.get("Root") == RootName and Lock.get("Path") == RelativePath: Lock["Users"] = Usernames Lock["Groups"] = GroupNames SaveLocks(Locks) return Locks.append({"Root": RootName, "Path": RelativePath, "Users": Usernames, "Groups": GroupNames}) SaveLocks(Locks) def RemoveLock(RootName: str, RelativePath: str) -> None: Locks = [ Lock for Lock in LoadLocks() if not (Lock.get("Root") == RootName and Lock.get("Path") == RelativePath) ] SaveLocks(Locks) def GetInheritedLock(RootName: str, DirectoryRelativePath: str) -> dict[str, Any] | None: CurrentPath = DirectoryRelativePath while CurrentPath: Lock = GetLockForPath(RootName, CurrentPath) if Lock is not None: return Lock CurrentPath = CurrentPath.rsplit("/", 1)[0] if "/" in CurrentPath else "" return None def IsPathAccessible( RootName: str, RelativePath: str, Username: str, UserGroups: list[str] ) -> bool: for Lock in LoadLocks(): if Lock.get("Root") != RootName: continue LockPath = Lock.get("Path", "") if not LockPath: continue if RelativePath == LockPath or RelativePath.startswith(f"{LockPath}/"): PermittedUsers = Lock.get("Users", []) PermittedGroups = Lock.get("Groups", []) IsPermittedByUser = Username in PermittedUsers IsPermittedByGroup = any(Group in PermittedGroups for Group in UserGroups) if not IsPermittedByUser and not IsPermittedByGroup: return False return True def RequirePathAccessible(RootName: str, RelativePath: str, CurrentUser: dict[str, Any]) -> None: if not IsPathAccessible( RootName, RelativePath, CurrentUser["Username"], CurrentUser.get("Groups", []), ): raise HTTPException(status_code=404, detail="Not found.")