Files
granthi-sync/server/__pycache__/granthi_link.cpython-314.pyc
T

116 lines
18 KiB
Plaintext
Raw Normal View History

+
¦,…j1ãó@Rt^RIt^RIt^RIt^RIt^RIt^RIt^RIt^RIt^RI t ^RI
t
^RI t ^RI
t ^RIHtHt^RIHtHtRt]P(!R4tRt]P.!R4tRRltR t!R
R 4t!R R
]4tRtRt]R8Xd
]!4R#R#)a granthi-link: provisioning service for the granthi-sync product spine.
Bridges shre-id (Zitadel) identity to a Granthi (Gitea) forge:
POST /v1/link {zitadel_access_token, device_name}
-> validates the token against Zitadel userinfo
-> ensures a Gitea user exists (admin API)
-> mints a scoped Gitea token for that user (admin basic
auth + `Sudo:` header -- the only mechanism that works
on Gitea 1.27; token-authenticated sudo returns 401)
-> {gitea_base, login, token, token_name}
POST /v1/repos {token, name, private} -> creates a user repo with the
USER token, returns clone URLs rebased onto the public
gitea_base (container ROOT_URL may not resolve for
clients on the tailnet).
GET /health -> {"status": "ok", ...}
Design decisions (documented per spec):
* User creation uses source_id 0 (local). Gitea 1.27.1 has no
/api/v1/admin/identity-auth-sources endpoint (404); the shre-id OAuth2
source is ID 1 (discovered via `gitea admin auth list`), but users
created against an OAuth2 source cannot basic-auth and admin-created
users get no external_login_user row anyway, so SSO linking happens on
first OIDC web login (by email) regardless. Local + random password is
the simplest correct v1.
* Token minting NEEDS the admin password (basic auth + Sudo header).
The admin API token alone cannot mint user tokens on 1.27. The config
therefore carries admin_login/admin_password alongside admin_token;
config must be root-owned 0600.
* test_mode: when config "test_mode" is true, a /v1/link body may carry
"test_userinfo" (dict) instead of a Zitadel round-trip. This exists so
E2E can exercise the ensure-user+mint path without a human OAuth login.
NEVER enable in production config.
Stdlib only. Python 3.9+.
N)ÚdatetimeÚtimezone)ÚBaseHTTPRequestHandlerÚThreadingHTTPServerz1.0.0ú granthi-linkz/opt/granthi-link/config.jsonz[^a-zA-Z0-9._-]+cóxRp\T;'g/4pVPRR\ 24Ve7\P!V4P 4pVPRR4\ PPWW`R7p\ PPWtR7;_uu_4pVP4p VPp
RRR4X
X 'd\P$!T 43#/3# +'giL4;i \ PPd'p T P4p T Pp
Rp ? LwRp ? i\ PP\ 3dp RR \#T 4/3uRp ? #Rp ? ii;i \&dX
R
X P)R R 7/3u#i;i)
z@Minimal JSON-over-HTTP helper. Returns (status, parsed_or_text).Nz
User-Agentú
granthi-link/ú Content-Typeúapplication/json)ÚdataÚheadersÚmethod)ÚtimeoutiWÚerrorÚrawÚreplace)Úerrors)ÚdictÚ
setdefaultÚVERSIONÚjsonÚdumpsÚencodeÚurllibÚrequestÚRequestÚurlopenÚreadÚstatusrÚ HTTPErrorÚcodeÚURLErrorÚOSErrorÚstrÚloadsÚ
ValueErrorÚdecode) r
Úurlr Úbodyrr ÚhdrsÚreqÚresprrÚes &&&&& Ú/private/tmp/claude-502/-Users-aibot/7c1e3ba6-d022-4b1b-8026-9f6da66762d2/scratchpad/granthi-sync/tests/../server/granthi_link.pyÚ http_jsonr.DsZà €DÜ 
˜ €Dà‡OOL M´Ð";Ô ÒÜzŠz˜×(ˆØ ˜Ð(:Ô
.‰.×
Ñ
 ¸Ð
Ó
M€Cð
^‰^×
# 
#×
9¸TØ—)‘)“+ˆCØ—[‘[ˆF÷¯#”t—z’z #“Ð5°2Ð
9ûô <‰<× Øf‰f‹hˆØûÜ L‰L× !¤7Ð Wœc !%ûð&ûô ô˜˜sŸz™z°˜=úslÂ
(DÂ5C>ÃDÃFÃ$FÃ;FÃ> D Ä DÄDÄFÄ0EÅ&FÅ8FÅ9F
ÆFÆ
FÆ!F9Æ8F9cóz\P!V RV 2P44P4pRV 2#)Ú:zBasic )Úbase64Ú b64encoderr&)ÚloginÚpasswordÚtoks&& r-Ú_basicr6]s;Ü
×
Ò
˜e˜W A h 0×
:×
C€CØ
C5ˆócó`a]tRt^ftoRtRt]R4tRtRt Rt
Rt Rt R t
R
tVtR #) Ú LinkServicecóÚWnVR,PR4VnVPRVP4PR4VnVPRR4VnR#)Ú
gitea_baseÚpublic_gitea_baseÚzitadel_userinfoz#https://id.shre.ai/oidc/v1/userinfoN)ÚcfgÚrstripÚgiteaÚgetÚ public_giteaÚ userinfo_url)ÚselfÚconfigs&&r-Ú__init__ÚLinkService.__init__gs[ØŒð˜LÕÓŒ
Ø"ŸJ™JÐ':¸D¿J¹JÓG×NÈsÓÔØ"ŸJ™JØ Ð EóGˆÖr7có¤\RVPRRV 2/R7wr#V^È8wd RRV R23#\V\4'dRV9dR#VR3#) ÚGETÚ
AuthorizationzBearer ©r Nz&zitadel userinfo rejected token (HTTP Úsub)Nz zitadel userinfo returned no sub)r.rDÚ
isinstancer)rEÚ access_tokenrÚinfos&& r-Úvalidate_tokenÚLinkService.validate_tokenqskÜ Ø 4×° ¨~Ð&>ÐA ˆð SŒ=ØÐAÀ&ÀÈÐ ˜$¤×°dÔ):Ø Tˆr7có`VPR4;'gRpV'g5VPR4;'gRpVPR^4^,pVPR^4^,p\PRV4P R4P 4pT;'gR#)Úpreferred_usernameÚÚemailÚ-z-._N)rBÚsplitÚ
LOGIN_SAFErNÚstripÚlower)ÚuserinfoÚcandrWs& r-Ú derive_loginÚLinkService.derive_login{à|‰|Ð7°RˆßØ—L‘L Ó/¨Rˆ—;;˜s & )ˆz‰z˜#˜! $ˆÜ~‰~˜c (×.¨uÓ5×=ˆØ|ˆ|r7có2RRVPR, 2/#)rKútoken Ú admin_token)r?©rEs&r-Ú
_admin_hdrÚLinkService._admin_hdr†sØ 6¨$¯(©(°=Õ*AÐ)BÐ!CÐDr7cóf\RVP RV 2VP4R7wr#V^È8H#)rJú/api/v1/users/rL)r.rArf)rEr3rÚ_s&& r-Ú user_existsÚLinkService.user_exists‰s6ÜØ d—j‘j°¨wÐÇÁÓARôT‰ ˆà˜‰}Ðr7cóˆa\P\P,oRPV3Rl\ ^444pVP R4;'gV R2pRVRVRVRRRR R
VP R R4/p\
R VP R
2VP4VR7wrgV^É8wd RV RV 2#R#)rVc3óP<"TFp\P!S4xK R#5i)N)ÚsecretsÚchoice)Ú.0rjÚ pw_alphabets& €r-Ú <genexpr>Ú*LinkService.create_user.<locals>.<genexpr>søéи1œ7Ÿ>š>¨+×6à ùsƒ#&rWz@users.noreply.granthi.shre.aiÚusernamer4Úmust_change_passwordFÚ
visibilityÚprivateÚ full_nameÚnameÚPOSTz/api/v1/admin/users©r r(z%gitea admin user create failed (HTTP ú): N) ÚstringÚ
ascii_lettersÚdigitsÚjoinÚrangerBr.rArf) rEr3r^r4rWr(rr+rrs &&& @r-Ú create_userÚLinkService.create_userŽø€Ü×*¬V¯]©]Õ Ø—7‘7ÔÀbÄ ÓØ ˜WÓQ¨E¨7Ð2PÐ)Qˆà ˜Ø  ˜Ø " EØ ˜)Ø ˜Ÿ f¨bÓ

ˆô t—zzlÐ"5ЗO%¨Dô2‰ ˆð SŒ=Ø:¸6¸(À#ÀdÀVÐ r7c
 óÐ\PRT;'gR4R,pRPV\P!\
P 4PR44p\RVP RV R2R \VPR
,VPR ,4R V/R
VRRR./R7wrVV^É8wd RRRV RV 23#VPR4VR3#)zwAdmin basic auth + Sudo header. Verified on Gitea 1.27.1:
token-auth sudo (header or ?sudo=) -> 401; basic+Sudo -> 201.rYÚdevice:Né(Nzgranthi-sync-{}-{}z%Y%m%dT%H%M%SZr{riz/tokensrKÚ admin_loginÚadmin_passwordÚSudorzÚscopeszwrite:repositoryz
write:userr|Nztoken mint failed (HTTP r}Úsha1)
r[rNÚformatrÚnowrÚutcÚstrftimer.rAr6r?rB)rEr3Ú device_nameÚsafe_devÚ
token_namerr+s&&& r-Ú
mint_tokenÚLinkService.mint_token¢ô—>> # {×'>Ð'>°hÓÕDˆØ)× ”h—ll¤8§<¡<Ó0×9Ð:JÓMˆ
ä Ø t—z‘zl .°°°wФؗH‘H˜]Õ+¨T¯X©XÐ6FÕ-Gó"Ià˜ðð
˜*ØÐÐ@ôA ˆð SŒ=ؘÐ!9¸&¸ÀÀTÀFÐ x‰x˜Ó ¨TÐ1r7c óôVPR4;'gRpVPPR4'dE\VPR4\4'd \P R4VR,pM@VPR4pV'gRRR/3#VP
V4wr5V'dR RV/3#VPV4pV'gR
RR /3#VPV4'g6VPWc4pV'dR RV/3#\PR
V4VPWb4wrxpV'dR RV/3#\PRW†4^ÈRVPRVRVRV/3#)rr†Ú test_modeÚ
test_userinfoz)TEST-MODE link request (stubbed userinfo)Úzitadel_access_tokenérzzitadel_access_token requirediz&could not derive a login from userinfoéözcreated gitea user %szminted token %s for %sr;r3Útokenr“)
rBr?rOrÚLOGÚwarningrRr`rkrQr”rC) rEr(rr^Úerrr3Ú gitea_tokenr“s && r-ÚlinkÚLinkService.link¶sdØ—h‘h˜}Óˆ Ø 8‰8<‰<˜ × °D·H±H¸_Ó4MÌt×)TÒ)TÜ K‰KÐ ˜OÕ,‰Hà—H‘HÐ4ˆØ˜WÐ&EÐ ×Ó6‰MˆHߨ˜W c˜NÐ×! +ˆßؘÐ"JÐ ×Ñ ××" 3ˆØ˜W c˜ H‰HÐ,¨eÔ 4Ø'+§¡°uÓ'JÑ$ˆ  ß ؘ #˜Ð  Ð)¨:Ô\ 4×#4Ñ#4°g¸˜k¨<¸ðEðEð Er7cóüVPR4pVPR4pV'd V'gRRR/3#\RVP R2RRV 2/RVR \VPR R
44R R R
R/R7wrEVR8Xd RRRV R2/3#V^É8wd
RRRV RV 2/3#VPRR4p^ÈRVPR4RVR VPR 4RVP RV R2RVP RV 2/3#)rzrztoken and name requiredr{z/api/v1/user/reposrKrcrxdefault_branchÚmainÚ auto_initFr|i™zrepo z already existsrzrepo create failed (HTTP r}ryrVÚ clone_urlr<z.gitÚhtml_url)rBr.rAÚboolrC)rEr(rzrr+rys&& r-ÚreposÚLinkService.reposÑsBØ˜ÓØx‰x˜ÓˆßŸDؘÐ";Ð  Ø t—zzlÐ"4Шu¨gÐ&6И$  ¬4°·±¸ÀDÓ0IÓ+JØ" F¨K¸ð@ôA ˆð