"""Shared authentication and API helper for Gitea scripts. Pulls credentials from the macOS keychain via `git credential fill` so no tokens appear on the command line. """ import json import base64 import subprocess import urllib.request import urllib.error # Known Gitea instances — shared by all scripts. REMOTES = { "dadeschools": { "host": "gitea.dadeschools.net", "org": "Contractor", "repo": "Timesheet", }, "prgs": { "host": "gitea.prgs.cc", "org": "Scaled-Tech-Consulting", "repo": "Timesheet", }, } def get_credentials(host): """Return (user, password) for *host* via ``git credential fill``.""" p = subprocess.Popen( ["git", "credential", "fill"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, ) out, _ = p.communicate(f"protocol=https\nhost={host}\n\n") user = password = "" for line in out.splitlines(): if line.startswith("username="): user = line.split("=", 1)[1] elif line.startswith("password="): password = line.split("=", 1)[1] return user, password def get_auth_header(host): """Return an ``Authorization: Basic …`` header value for *host*.""" user, password = get_credentials(host) if not user or not password: return None token = base64.b64encode(f"{user}:{password}".encode()).decode() return f"Basic {token}" def resolve_remote(args): """Given parsed argparse args with --remote/--host/--org/--repo, return (host, org, repo) with overrides applied.""" profile = REMOTES[args.remote] host = args.host or profile["host"] org = args.org or profile["org"] repo = args.repo or profile["repo"] return host, org, repo def add_remote_args(parser): """Add the standard --remote/--host/--org/--repo arguments to a parser.""" parser.add_argument( "--remote", choices=sorted(REMOTES), default="dadeschools", help="Known Gitea instance (default: dadeschools).", ) parser.add_argument("--host", help="Override the Gitea host.") parser.add_argument("--org", help="Override the owner/org.") parser.add_argument("--repo", help="Override the repository.") def api_request(method, url, auth_header, payload=None): """Make an authenticated JSON request to the Gitea API. Returns parsed JSON on success, raises on HTTP errors. """ data = json.dumps(payload).encode("utf-8") if payload is not None else None req = urllib.request.Request(url, data=data, method=method) req.add_header("Authorization", auth_header) req.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(req) as resp: body = resp.read().decode("utf-8") return json.loads(body) if body else None except urllib.error.HTTPError as e: error_body = e.read().decode("utf-8", errors="replace") raise RuntimeError(f"HTTP {e.code}: {error_body}") from e def repo_api_url(host, org, repo): """Return the base API URL for a repo: https://host/api/v1/repos/org/repo""" return f"https://{host}/api/v1/repos/{org}/{repo}"