# -*- coding: utf-8 -*- r'''判定 EzHub 令牌到底有没有写权限(无副作用)。 方法:对一个**不存在的**资源发写请求。 * 令牌没有写权限 -> 服务端先拦权限,返回 403 * 令牌有写权限 -> 通过权限检查,然后发现资源不存在,返回 404 所以 403 / 404 这个差别就能说明问题,而且不会真的改到任何东西。 ''' from __future__ import annotations import json import sys import urllib.error import urllib.request TOKEN = sys.argv[1] if len(sys.argv) > 1 else "" if not TOKEN: print(" 用法: python probe_token.py <令牌>") raise SystemExit(1) BASE = "https://git.261826.xyz/api/v1" HEAD = {"Authorization": "token " + TOKEN, "User-Agent": "ykxmz-probe"} def probe(method: str, path: str, payload: dict | None = None, label: str = "") -> int: data = json.dumps(payload).encode() if payload else None headers = dict(HEAD) if data: headers["Content-Type"] = "application/json" req = urllib.request.Request(BASE + path, data=data, method=method, headers=headers) try: with urllib.request.urlopen(req, timeout=30) as resp: print(" %-10s %-46s -> %s" % (method, path, resp.status)) return resp.status except urllib.error.HTTPError as exc: body = exc.read().decode("utf-8", "replace")[:110] print(" %-10s %-46s -> %s %s" % (method, path, exc.code, body)) return exc.code except Exception as exc: # noqa: BLE001 print(" %-10s %-46s -> 异常 %s" % (method, path, exc)) return 0 print(" 一、读权限(应该 200)") probe("GET", "/user") probe("GET", "/repos/xmz/ykxmz") print() print(" 二、写权限判定(对不存在的资源写)") print(" 403 = 没写权限;404 = 有写权限(只是资源不存在)") del_code = probe("DELETE", "/repos/xmz/ykxmz/releases/99999999") print() patch_code = probe("PATCH", "/repos/xmz/ykxmz/releases/99999999", {"body": "probe"}) print() post_code = probe("POST", "/repos/xmz/ykxmz/releases", {"tag_name": "", "name": "", "body": ""}) print() print(" 三、结论") if del_code == 404 or patch_code == 404: print(" ✓ 令牌**有写权限**(返回 404 说明权限过了,只是资源不存在)") print(" -> HTTPS push 的 403 是另一个原因,需要再查") elif del_code == 403 or patch_code == 403: print(" ✗ 令牌**没有写权限**(403)") print(" -> 需要重新生成令牌,勾上 write:repository") else: print(" ? 结果不明确:DELETE=%s PATCH=%s POST=%s" % (del_code, patch_code, post_code)) print() print(" 四、顺带看一下令牌信息") try: req = urllib.request.Request(BASE + "/user", headers=HEAD) with urllib.request.urlopen(req, timeout=30) as resp: for name in ("X-Oauth-Scopes", "X-Oauth-Client-Id"): value = resp.headers.get(name) if value: print(" %s: %s" % (name, value)) me = json.loads(resp.read().decode()) print(" 用户: %s (id=%s, admin=%s)" % (me.get("login"), me.get("id"), me.get("is_admin"))) except Exception as exc: # noqa: BLE001 print(" 读取失败: %s" % exc)