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

119 lines
18 KiB
Plaintext
Raw Normal View History

+
ž+…j—0ãó@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óFRp\T;'g/4pVe7\P!V4P4pVP RR4\
P PWW`R7p\
P PWtR7;_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
RX P'R R
7/3u#i;i) z@Minimal JSON-over-HTTP helper. Returns (status, parsed_or_text).Nú Content-Typeúapplication/json)ÚdataÚheadersÚmethod)ÚtimeoutiWÚerrorÚrawÚreplace)Úerrors)ÚdictÚjsonÚdumpsÚencodeÚ
setdefaultÚurllibÚrequestÚRequestÚurlopenÚreadÚstatusrÚ HTTPErrorÚcodeÚURLErrorÚOSErrorÚstrÚloadsÚ
ValueErrorÚdecode) r Úurlr Úbodyr
r
Ú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,DsEà €DÜ 
˜2Ó €DØ ÒÜ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Á4(C8ÂC%Â9C8ÃE<à E<Ã"E<Ã% C5 Ã0C8Ã5C8Ã8E9ÄD8Ä8&E9ÅE9Å E4Å.E9Å4E9Å<!F ÆF cóz\P!V RV 2P44P4pRV 2#)Ú:zBasic )Úbase64Ú b64encoderr$)ÚloginÚpasswordÚtoks&& r+Ú_basicr4[s;Ü
×
Ò
˜e˜W A h 0×
:×
C€CØ
C5ˆócó`a]tRt^dtoRtRt]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__es[ØŒð˜LÕÓŒ
Ø"ŸJ™JÐ':¸D¿J¹JÓG×NÈsÓÔØ"ŸJ™JØ Ð EóGˆÖr5có¤\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,rBÚ
isinstancer)rCÚ access_tokenrÚinfos&& r+Úvalidate_tokenÚLinkService.validate_tokenoskÜ Ø 4×° ¨~Ð&>ÐA ˆð SŒ=ØÐAÀ&ÀÈÐ ˜$¤×°dÔ):Ø Tˆr5có`VPR4;'gRpV'g5VPR4;'gRpVPR^4^,pVPR^4^,p\PRV4P R4P 4pT;'gR#)Úpreferred_usernameÚÚemailÚ-z-._N)r@ÚsplitÚ
LOGIN_SAFErLÚstripÚlower)ÚuserinfoÚcandrUs& r+Ú derive_loginÚLinkService.derive_loginyà|‰|Ð7°RˆßØ—L‘L Ó/¨Rˆ—;;˜s & )ˆz‰z˜#˜! $ˆÜ~‰~˜c (×.¨uÓ5×=ˆØ|ˆ|r5có2RRVPR, 2/#)rIútoken Ú admin_token)r=©rCs&r+Ú
_admin_hdrÚLinkService._admin_hdr„sØ 6¨$¯(©(°=Õ*AÐ)BÐ!CÐDr5cóf\RVP RV 2VP4R7wr#V^È8H#)rHú/api/v1/users/rJ)r,r?rd)rCr1rÚ_s&& r+Ú user_existsÚLinkService.user_exists‡s6ÜØ d—j‘j°¨wÐÇÁÓARôT‰ ˆà˜‰}Ðr5cóˆa\P\P,oRPV3Rl\ ^444pVP R4;'gV R2pRVRVRVRRRR R
VP R R4/p\
R VP R
2VP4VR7wrgV^É8wd RV RV 2#R#)rTc3óP<"TFp\P!S4xK R#5i)N)ÚsecretsÚchoice)Ú.0rhÚ pw_alphabets& €r+Ú <genexpr>Ú*LinkService.create_user.<locals>.<genexpr>Žsøéи1œ7Ÿ>š>¨+×6à ùsƒ#&rUz@users.noreply.granthi.shre.aiÚusernamer2Ú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Úranger@r,r?rd) rCr1r\r2rUr&rr)rps &&& @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Ð r5c
 óÐ\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.rWÚdevice:Né(Nzgranthi-sync-{}-{}z%Y%m%dT%H%M%SZryrgz/tokensrIÚ admin_loginÚadmin_passwordÚSudorxÚscopeszwrite:repositoryz
write:userrzNztoken mint failed (HTTP r{Úsha1)
rYrLÚformatrÚnowrÚutcÚstrftimer,r?r4r=r@)rCr1Ú 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Ð1r5c óô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 %sr9r1Útokenr)
r@r=rMrÚLOGÚwarningrPr^rirrOrrA) rCr&rr\Úerrr1Ú gitea_tokenrs && 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ð Er5cóü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#)rxr˜rztoken and name requiredryz/api/v1/user/reposrIrarvdefault_branchÚmainÚ auto_initFrzi™zrepo z already existsr™zrepo create failed (HTTP r{rwrTÚ clone_urlr:z.gitÚhtml_url)r@r,r?ÚboolrA)rCr&rxrr)rws&& r+ÚreposÚLinkService.reposÏsBØ˜ÓØx‰x˜ÓˆßŸDؘÐ";Ð  Ø t—zzlÐ"4Шu¨gÐ&6И$  ¬4°·±¸ÀDÓ0IÓ+JØ" F¨K¸ð@ôA ˆð
SŒ=ؘ E¨$¨¨Ð"?Ð SŒ=ؘÐ$=¸f¸XÀSÈÈÐ"OÐ —HH˜[¨"Ó-ˆ ØØ D—HH˜$ k°9Ø t—x‘x  Ó ˜D×.¨a° ¨{¸$Ð ˜4×-¨Q¨y¨kÐ
ð
ð
r5)r=r?rArBN)Ú__name__Ú