From 09cdd62e6201d155bb9f8e46e90de82b572d0a39 Mon Sep 17 00:00:00 2001 From: antoinetoussaint-byte <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sun, 25 Oct 2026 22:58:28 -0400 Subject: [PATCH 01/12] wip: protos, interfaces and regenerated bindings for the solution-principal contract --- interfaces/authority/interface.codefly.yaml | 4 +- interfaces/composition/interface.codefly.yaml | 2 +- .../installation/interface.codefly.yaml | 4 +- .../contract/catalog.codefly.json | 2 +- .../authority-client/contract/contract.binpb | Bin 374847 -> 381342 bytes .../go/gen/host/authority/v1/authority.pb.go | 296 ++++++++--- .../host/authority/v1/authority_grpc.pb.go | 76 ++- .../gen/host/composition/v1/composition.pb.go | 22 +- .../host/installation/v1/installation.pb.go | 487 ++++++++++++++---- .../installation/v1/installation_grpc.pb.go | 132 ++++- .../authority-client/library.codefly.yaml | 2 +- .../_gen/host/authority/v1/authority_pb2.py | 14 +- .../host/authority/v1/authority_pb2_grpc.py | 55 ++ .../host/composition/v1/composition_pb2.py | 40 +- .../host/installation/v1/installation_pb2.py | 46 +- .../installation/v1/installation_pb2_grpc.py | 115 ++++- .../src/gen/host/authority/v1/authority_pb.ts | 121 ++++- .../gen/host/composition/v1/composition_pb.ts | 14 +- .../host/installation/v1/installation_pb.ts | 258 ++++++++-- modules/authority/module.codefly.yaml | 6 +- .../services/edge/service.codefly.yaml | 4 +- .../services/hop/service.codefly.yaml | 2 +- .../pkg/gen/host/authority/v1/authority.pb.go | 296 ++++++++--- .../host/authority/v1/authority_grpc.pb.go | 76 ++- .../authorityv1connect/authority.connect.go | 67 ++- .../gen/host/composition/v1/composition.pb.go | 22 +- .../host/installation/v1/installation.pb.go | 487 ++++++++++++++---- .../installation/v1/installation_grpc.pb.go | 132 ++++- .../installation.connect.go | 126 ++++- .../installation/v1/installation.swagger.json | 2 +- .../proto/host/authority/v1/authority.proto | 44 +- .../host/composition/v1/composition.proto | 7 +- .../host/installation/v1/installation.proto | 95 +++- .../console/services/api/service.codefly.yaml | 2 +- 34 files changed, 2522 insertions(+), 536 deletions(-) diff --git a/interfaces/authority/interface.codefly.yaml b/interfaces/authority/interface.codefly.yaml index 72c484f2..abae8de9 100644 --- a/interfaces/authority/interface.codefly.yaml +++ b/interfaces/authority/interface.codefly.yaml @@ -2,10 +2,10 @@ kind: interface publisher: codefly.dev name: authority description: What a module or solution calls on its host to be issued, checked and verified authority. Implemented by accounts and by the local authority. -version: 0.2.0 +version: 0.3.0 type: connect protobuf: package: host.authority.v1 services: - name: AuthorityService - methods: [Issue, Check, Keys, ListResources, Delegate, RevokeDelegation, GetDelegation] + methods: [Issue, Check, Keys, ListResources, AuthorizeTransfer, Delegate, RevokeDelegation, GetDelegation] diff --git a/interfaces/composition/interface.codefly.yaml b/interfaces/composition/interface.codefly.yaml index a42df9e5..72797d86 100644 --- a/interfaces/composition/interface.codefly.yaml +++ b/interfaces/composition/interface.codefly.yaml @@ -2,7 +2,7 @@ kind: interface publisher: codefly.dev name: composition description: How the host learns what is composed; the ceiling is the manifest. -version: 0.1.0 +version: 0.2.0 type: connect protobuf: package: host.composition.v1 diff --git a/interfaces/installation/interface.codefly.yaml b/interfaces/installation/interface.codefly.yaml index f55e1055..78ddd757 100644 --- a/interfaces/installation/interface.codefly.yaml +++ b/interfaces/installation/interface.codefly.yaml @@ -2,10 +2,10 @@ kind: interface publisher: codefly.dev name: installation description: What is installed at an organisation and who may use it. -version: 0.1.0 +version: 0.2.0 type: connect protobuf: package: host.installation.v1 services: - name: InstallationService - methods: [Install, GrantToTeam, List] + methods: [Install, GrantRole, RevokeRole, ListRoleGrants, List] diff --git a/libraries/authority-client/contract/catalog.codefly.json b/libraries/authority-client/contract/catalog.codefly.json index 228f1463..8ef2af03 100644 --- a/libraries/authority-client/contract/catalog.codefly.json +++ b/libraries/authority-client/contract/catalog.codefly.json @@ -10,7 +10,7 @@ "kind": "protobuf", "package": "api", "path": "contract.binpb", - "digest": "sha256:9fcee9f110613a97881cfdfd035de3b82b7b58cac138e1093a47ba4460436d72", + "digest": "sha256:252acddd2c15e5fa8d0a825af6c817af88af20f4698250909bbe70cd376786b7", "services": [ { "name": "GatewayService", diff --git a/libraries/authority-client/contract/contract.binpb b/libraries/authority-client/contract/contract.binpb index e2bea10c30bc456ebbdf86a76f3089dc928f95d1..6bd5b63a55d73ba20a952fea466e9fad9dc96087 100644 GIT binary patch delta 13882 zcmc&*Yj9k}m7czHdo*)pOZJsy%VSHrHpUOV@Dt0jjj{0qgNDz!4JS-V;gWAV*J23h6*GhgeX-MMQxJhPeLBcvPH642t}$0p~!}?Swd>7vfnw~ zcO>DYYX9vIyXN%mbNci-r%!i(ee}j3C!D=FasNSa&^j1AXn$AUQX72lm>AkvE00FX z{wAFA75&-nR621_yd@n=W;)|(RWn~!s_MCNsan5K&d60ch3R-E)t_#U?@Dx(+n$&e zX?Fakbsg?BXKZIW)w?S-kc_9Pi{vha)YwFA=rhA9&Zc(tr4z~aL|?3jdWE>x*lcQ} z-@2}2TDvppD+;y7)6MZrUn-f2yHlMpqeKWjP`(WVUFD38`7`R+)izjeai1~6jVlMI zofqo8g>p}WTkVYZC-PPGi~SE~3M&g7*&EBYch5T$8fv{(o)#05&iJn=8&A76>VrC2 z620O>bNtT!cqSXI7~>ZnaO z%MvxVM2^YTdoA5@uRYe&1I9$gg8*+&BH1xB4-o9gKAiPvI`M0>Xv z%Vsen-8(-5kC}n|4)jWS2#SoC$a=}tfaA>?6(_T6y*{wTli32&5o0P*Axm<7@pL8y z4KvW7s}bsC`r{oAzNb_DUEQo(HkR&+XTfgb33?`ynJn~=WfR1U(PJGQke-Qmc-a&) z>FL!w-nuN!5uH;5_zHU^_4odC8p`BI=V3FX(p|A+;vOwG1+#iazf`9~6PuNc-^JEW zvDGuFo_o9rh%d zlGIM5i5CJ!>e+?0oXyb4m_2-{Rfd(?^&Gu)st2LMLh;o%qemlN<#+?>L^ke!r8t13 zo*s-H>+pIKcg5?~;3_#S=ke6x8Iw6OI*c;eR60(>Cc)93N@EVJLw=MPMkW(4;dUgS zu0%4{<9O{D0K6U?IjDK`s;zgNF^;707l{ARL_z_TDU!So7&XTijJp90T< z#haYhEoLfkgNuh2t&mGZeF3-@he}Mp0&p!Zo#j*DT0DQ5bCYA)0oPs@=u+ob%A3Y5 z3zs^J9NP|r(YoB)sQ$WAE~;1_<~iE|D*&Zd+T)!gm==+s)Gas2N2^!58$<;NhCRX`9(0|pms|;nK|604mxugex(rZnm-8qp1q{ohKwQLbDZiRxtw+>W;N?O?7i*yx#-;n@h_K6@ttb%W-yae+<{6 ze!f=jY-lND4`NXAXUoJ2zdyKJJb$zT46wy2aBg94xO0o@yisnf-V$a{g1Z3yw@mP7 z7e@c>&~A|Xhe4>{+$i_fY>)Wz!|1;~FF%6*JJJ7)o__??(BXBmQq=5>_%4iqV`tul z4yF*ZN}coCB@QU{vzz3zHLxmivXQ#vEA;V1-0 zXW2w&E&@=v_U=Hh`sI2#cgpUtHlwftkkSJwOEED}Ak>Tv^16zih&ChIAfsne)OkTb zh~?UM2JTk(Z;&rcxihS-W3iA7gv@~uP;M}z*4!fJRc9i_tO(j5B;#t^T9$zC`r*6H z+#hc0SI^!eTO0a~@6dx9jqgB#yWiN>vMi`PfYCNFH)3}{UDG5tR}4hPnb9otA1Enx zRzTyREB6Kt3w5?hUZ2|=)|Lsf%J)J@A%uW(gZElGc0pk5d(HGgLDs#PK1FHJ5*T}5 zo(&!MSvmmli74Y-P+;3UC1Wav6 zNP0lP)|$ksv=5LTT9S0HBeKl@3f=<(Q!t&udqCvp5CZQZVTGI;bVLM>sfuPf5G!3 zBl0Uv4-$yEJsB}rLz&bNH=ss z1Rhttn`Kke2@$U1PLg#v>FWT}wLxheKtb+FUk8v7c#?Ej4}l>!bV>wH3lY6e-MJYD zvdR{@VEri(_E>U=HUL`*YYdwmBvX3opdeX^u@n8ikgNnU@B3u80yp%G2%J}ETjYE3 zX9U(c8LuGX+Gm9|mgN;hAn&Xw9K!-YLEcCbYn(}DbX@zKuqb8Oc7X%Na{?*B|D?^+pJaxS?-| zz&F)(TjkoR-w;SELr2oX8Juc{wyll674`g%IF*3kzZ(4$~;wTFaN%$tj2!JI>?YP;N6H6+IAsi6@pL&UNQ z>K3}8*F@l_>ezO9L*;7%hXWE+$m+gM>dwa4AYJ=)^`F~i&4Sl`b+0(C)6h~DJ2s9noowys-cWBnTfd#WCSRDVNYQ`IC=tNkXie<#TN(EZJ!pWQ0I zE9&3$vrQo@|E4h6rqH5X^%kx3?`bB8d~4|C9rF94>Mc>AQxtkpGchg3jK{d4Ux>gj z)k8aFL**}g2aVylf16A<3&VqS?YGr?J7sm<+rH`02tjZA)*iz%euq@1g9Av{en&0b zCD+uyBO-dCq7f|b2%LUsmSZRz+wT$!@3o*osuy<2XQ#aDCo?pH=Uqx>r0-bQ{@^K^Pmq%*Zx2i#N>jy4}1fn5wbt< z4Om3mq5UDH>uP4f6h2hlFV8gRlOT|;{W*pE)s6TKMLzd~9wfwl?g#xiR{GDxNI?(MwLy&z`UUgR z&L2$ZgM^Sj`=LLMr||^|p*RBR+Fz(09db?G7k&~)BUrxhH^+g~@Ng0ZIrKf$&|^9a8Ma zXq+A%2jK*2a&+gYaYldvd>Q}hp#If8k!>{B^gIrdW4K`?5;D>TbU1r#*akRsgUs82 z4v{BFw?@`YQx(x#*6m90gon|J25JCXbwLwk3H9A`lq=7Pm*)&iB z6j$RtJ|dGJhNAr`-zgA;VV)wVJVKeM)D4}r0x!W<`&6z|-jHLQ6UK3{jIvAem{T4i zB>)iU4nEd;clO765}gTu9Z+^C#b9SGoV1Fdbt!O-&^gO`!3wh5K;!c{E3D7@rEJR! zI6*o!)SGN+U9i;nE;(ZXDD=HxMfC9$B=o*u73)hzDVygaj(EIFfOPGPmfF)Lr#FCL zs?Z;WW_;1Yje-_xDQEag#7PbW>Dr(sL~GRCZaG2yN0*FZa1waQD$Mgh#3jp|k4kwp zKTjf_pj$d<*M45rbj#y8{08UqR-xYVK+-qM?2@ItK3^oSp46OJpdhQF2L!v7mm`N_ zJ5p{MF7+2c#_R3RWWDa#UGYXNKfjs6*zXF*bYXc?AZsu32g)A!2T&A%AaXevXp|1G zZ4g~SbqhCv2?t=0Kj{=+2N)Y)G~D69=StCKDtgL+!uyE?31q-h}AHdtG^SXWiTA|YvzS_@J=GTn z=6pj>EYX{(UqNthrio@fyAjxC&mi%{`a2TwWP6;@^PU(G$}Tbw7JnQtCjq1YyBSP& z(1W1Q^q@D8$aZ(6V*}JV$GtoIGw4h<=h+VH&>riHwIzBISzaFo$Zm^w0wAsTRTC-8pMF5fElbph#?vonpqcUR$sqQK2fnQtkD|{ z&1^u}{gA?rv6&5OYfk=i^@dT{%m$oLXb6qXG~pmZn;@-*V3S(2UoLBCDr8iX+Ng2% zT)#hbXj-t)P=w8Ff-5z$1Ds=%dTqaKt=LpB$?t*wn{aSFN;)IJwgk4Ty8GqP+?KEg z#xypw6$i>ZHnSCogT`Nw8-iQI8t2j2%vK!H^*6$1w&mH-aa*2^RNj_nLtY4O1KVXcNkz1)&vyR26GcI1PX*&b3pE>ZbJ&v{mBPy zcyKcmxZAFt!3Swe&{>oF=3?1lylAdx_cV7HocP+nWzZ5+;6M6la9 zTv9>}?nZw)Jc9zEjvth_R_``5L>tE3ZDuGbjWS|LGXw=fl^((jdm;ocNNPj>9;97O z5pR<*1?sQG9ksLzq$wF81wxmRq zRi7M|i|1tXbE74f&Cjg>qYYv-dKLf!aO6R2O7vTndhK4hUY&nX-c!L@2PqdjcCSgp z{8B@^c1q28NN%keG`@xa=r-tTSQQk;8U#jgE#frf%6)tuzMWvZ=VIc#{>Qgvm-$Q-BbVauv}huNJI*GOGYCS zz#&njF@gf#+aDyB`Irt!*M3l4cSJ6%d(cNL&^q#FC|kq%3lrM4Zwhh*V{d%G347hegoldVE+5%6-R4ix$`tP}&nB@Ra)ZN9E?q zCj_!LX|AEPQ)G~7P>rFqQ)>NDIlbZmHJHF)+*!5m7{G+H z{=FQH5OmhRmq*y0=Ls30QyxgyKCixhOx9JN7v?00MzEZR_W3i$i5t~t$K=n)ep5K( z*3y$^GoCHg(#Pc7+)Sr<E@%kr z1~5!?3bOH7FMZO53?U*>oA52+jBk$LmD&?;PFeqtdL*2(^?27OTdbA*(90%i`M;WE z+9qd0UI6nq`vn$@oREGxE*D;KR^hxV_44C#!aN>|?QcF$Yjq;|#=MDjIR=|peR^3| zB8}%wsm;%(RLhUcC&jnbJICd}sz05O6^pMZ>~O9u>?n04`7u*o2+bnRnoz|jfw* z@pkMU#^pV6cP954(ZZcd_BchO?TI1~*ln^)9x?{@i$AD+{D^ja5mlETmtS6WV39r< zUlvg(?1(XLFc5H^-&>Br&Ke3-MBlg651x=)trbbG-N?~79`y+MhxJq^pYwGoK!!^e zmT(JO&hc;v_j*vS&_gF%KCgDfvN8NXVZjI~xU8B}ugB5gK`w3^(3%FsF&C2B+v6D< z5B^Qcdr~fm_~jRl>UdJlpA-(<Y zY*R&Q;wkw|t|-K325Q4}i;DPZm?&&CMKk9+U&Fo%5}QzowK#gz^76jZTCyqhjhM?c zC=TcVPulan-c)7)_+WDw6QGELD@ShAr6U^b;OSv@>pJ4SeW@(Mi0la4;pUYY2FgYr z?;Yhgl+AY ze2h$JwPvokrs4cU<{*27_-U0WSgvFoKid-p%aznDa-L*U=jePk!CDf1PJ?ZuO* z&mc3gtCx6G%`%8mAaijUUG@C^Z5dAH@~Kt7!i;)6W2W;Mt28J8%`VM5(vC&kVOG0EBxZSzL2H(b#&10EjhgOfsh+JrVim~c{AwXr>&RVOXH*?Cuo zGR}V$s#D2-yM_*>6(Qe6RHjm)U$Y^Cj;Iw=>zre_^U$m3w7}JB&uMwAXc`{Pa8(NN z%lmXy@RV$=o^I9z+8|_lk@l>9ho4^4$Ve9!)~vu>b@(Z{e#$I!*Tbs8VXzuIG;O!M z%Bs~Lo|1Ld)%lVGT36Nik^?&V*P{PM?hpNI)#|5Zb9Jq`Dd=dbH8%zL1^w&MpCDLJ zAk;rSEt{(A%_&oRuHKw7VL9}lgZ}HeKZc&88owrQ$<4{@L)M%FeKz3W0lO>+tQ64| z!_ndc)*PlfV%fVW?uy?XYtQ07Pf_d_XQe$_{|GS$fD|o~1?Ex&<3W`MsPYEfR;~*j z8mx-w%LaYX%T)d1jJ&!kt&8>uC8vwIDdfr*TPg1x)-`gXzY0u0Mgm4HcYWvAxQvcB zq#*;^WwFLcM65%v2vP|OHyF!kZ#NiE3BIE>LVXXG1m%Wqq2SKYT~0+gg5qLG~Px7EKesDp2r}u~&zE$b^D-t(gKtIBVg2 zJq1?NO58)CJ9U{+6n((I)`SBnD7w~!11>|5whq!Lmgs5(VnisRg_`ob6iVyzyc9|| zgSVV{5lS~BlnU9H6hry(N$kxgSdiQy<7N{qARvPfskJNvpEe*?8cnR^s0{S0Veo&6 z0?5Uf8%z*@BEcpIa7o0NO?kZ$1WgD6t+z=Tge;(64nmspbwuRhCU}_cMYyMI4r~*! z`{;ehcd~g@!=f6w=fEQ5Ptt9(!IU(<)e3DWBtf~s*1SIpOx%s~C;EWD)%ZliUaiI_ zx+G~Uq>(>C0UsOT&-uD8X=|RB{J9mp^bm`i;%$MQBD!?+l+@C=oRl+%#lJHdS_QX- zxtfw7FI2?~4zvT(ZraX>-J?Pa>=3KZ13y|RiSIlx-6 zK&~5@oY(B1nrX>Xs^|3I9q(Zj`yb|hf_P$h2G?*AEuVJdd| zA7MBHXtl|eT_zMYrq+c}q4`*i0=B+;L%7MUHp~N|PqZdZE0W&5e9>CCa-5UWO!9kTc zC#&Y*H~I|%IijIJLEIpKBTZ3G+iT`cm`89g&l_Bn&GwpkGtv>OzziMMd9LEk0gG}_! zdXPXS0{X(JCIhlZhj*ADk|Su(FDDCgIoU`OJ0p|2GsNZrziDggn z5j2ZYsPycxD5Q4)qQG}pOsrw!P7KovlCB2)ADr*t6|9g)gu3Nf`FXoGKb^W!5vU*M z@Ey~6#4lk9Tc`%MA0zXAq}j0~Ku(UntB;5I=bHR|S>G7(rx(d=B0H!)vt?-+U#lri znMXEUn3EmqPZ#9$9G!Yl;NFJIbZhInRb>BBwfo%F(|_Pli&G)ru7DZG@Y}33JRSA+ z;nYap#Sx1CCgZC+jh-sjFtj(X%a~ix&4DiWtMkGLy%U5FQb9u43NuyV7bGA=^rKdO z&iauo4s;-sO5=?Ui(QX{l4uCZV~SYDAc3Trudwh55uHMht%Y_ND4hoQiJeXJ zYINEv|-+7N3E7?S^N9WxwmEi zyT5b3*ZIEheCIpoKKP5gSN=Zvz?c{_#{y&4^R&9cymQ}pdj%a0igYZ({^bH%F2WCW+ZhfS#%<$E8tAt|RE|?4eP%^Y2-^x8rsV`?%IlZW zu_{a8Gni#(+Qj1~P_TSJ_66I1?~1r zreX>@fr{~mmQ#bM3WBR5IMJgEf~#WEY#)KEqNdSaVVjoWSaXfdGSosV^5%vn*>$#M z86k{Tn@xdesk@;+A!FCmM0raK6&6;9Y?fgerU6~G)&%=7mdFrDUTmQ|%W9n|J_*#+ zPA%||07IQM*=|zwu&z!Hw$ifly1Zha0`zs$rhCW){X%P^{g_sO1uT^BwbC!k7EW?~ z5-=>BKEp!+xNMPS+gp?ZOf8bnte~#)MUgz8f(N5hJ!C<_Qmo)_@EulwAT1rQT}cC? zY^mcLZGmHHaf!!4(6G!5+1Dxt2J%uHEiYRZp5Rk}e%Yj44+SB!*|hDgiXQ5kW&JAJ zR^J>^(S%ZtQgdFRPY>>9*VmQ-1+8X=y;^Z+0LV92(b}@skcuj}GeF-u*(axCYwlV}`5Yp#p-X&vWt*`I* zVh0Ky^kN4D?tag=rfI_DozT{6Oa*s3?L_vFm>tzKy|`fJrG1CuzKE@0aHNr?lc<+cbhu?m}2C`76@#+ zz1Rkn#Yr<1u{SGqFgYoIzn->^COwlu0)`|^)_QomrJ!e;qQ_LqoD|J7@igxz`NZ&1xyow%2r`3*F_h!I8LJxPfZR z^^$qF?#;N$b3hO>_U25mM-f5Gp`RPK3K31Z@{74NNnY4M+qT+v02%89ejd{G#8NTc z?->Z>1onF|1pwiGFQz~cz8^8Y3Nf`z)K2Swz|P}J0th5;*ht%o4+!i$j|N$EKol0) zYhe#L!7*X{gZz9WtywuHLdA-RSig0 zGrd20Twu3H;I53YW1SEt``7}2yb~glsRRH)-jxtLT|vcYJJ#L8NJTpu1b%vV|@b-6ulYnIYw;Gwi1hNCR}N zGqPe#l~+FJn~OmRdd@dD!UfE_z$_aT3(UPBFLl%AstW?=tQO1WUJy8FHAK0!USyUF z0mTD*UmRb1Jv}d~UKFS}9u=5g6eu|wqJUYi3T*p}nhCS7j#qD|7ew)^BEx4wnF1A< z7GRZ`PVhBh{JRYIQhm{Deo!*iE_j{YH5=*y9qV=3-AiSaulw%8AOyYcdpJ|A@)DcO z%>>1F2<$aN>%Uibc<_EkhI5Pnq>jSx_pUR6r5IBN03#a%8%)(nd#|i#b z7@x`0{nT9aSAm+&L^;ab4_P-00(7hoImKtz;2VZ~=qEZL#C_-|dX6&sBWC182Xw5D zIMF-7$Zq?{Pj^5te&nZnjxzldX5`!fbgWP0p9W}or24m9G$%O0FNN{7kWcTV|JnAX2o)+(6HEhP|BA!Tl@?H; z8U7>+$5$evOByDzj;}<%PG~@A_=*#H6BIa3@Ec*=W{T)axotN!tr-|`N8%~83tZ0~ zaDKx(EDKs3Tu6f73iF^DPaOHl&+o-4VASBYw+9sFt`zK6r%wt*qt$wZtu6kEUDV6^MI-qDQ;3LLR-7 z8tYd?)Ws4qxWQaeIuDsajL^fZm|yE5Zi(AaZFNJ9{<2M)M`?XwTSmT50s1!7hNEmV z*4=J&%G6OhI@%u6_YvI_cVN$^d*Tk$Z}1T2Ku1Wo*SaU}NH=)g6R%6NfpA@#jZI#c zW@D4rf$bVtfHdD=Y!lHMdEhQuRImXXfY+s)IKI{JRarH0o8-H9(SkDnPNTcGO(+|h zp0#fQ{nd&d`nJf1V>G*L3t9<}0&2H-7dbc%?Qa+S%$+z8fINDPww84vpENzvE70ZW z1{oCK?wWbEDpJU-n6XDrJWeeIG0$zP?F;n4g87ihU0r0P>yVv{`=rJh^Ha#AfY~TTWmZJHomn#uyCQf&IZo5@&f}079-)LNGNT zpE*Gr%Lcq)HXs1`_}$c5KNQjFg)wjsMRlqIfqSUD!jl|C)6aJ;4k_`=Lqb-cqy=*m>9ujU zpGdDQ1KLv1c9Ac(Wa4t3G7F+lnDSTuNNw_OC+Vii+xNLY>rcdQj3?cJ;gM8qaB##O zNXh;8(1x;<7fEo!sFb5GsX$PYLPu4L^g|)tYutS5UaB448&WSu7W!)}Y#;38{O4{P zKwlRDAZedxE)XQ`^UMvZ%j``sSF?feCUd@8Ii|4Ko4gGS#B-%{{5c4@sk$a&FM~aJ z;M#B8DPO&p8fWswhArRko!J?x03YDWqrkT50|Ec74_7V8j8_*bra8C-e~$+)D~8V~&_f@|<0QCjcwFp+3jgjG1tazDjK zFrZ_dmEWA9%A&LWF^xg6oQ3u2)9kh?x%4dkXYQ!&H1lggXQDG68|sMPuqQr}lHvi} zqz_h8Za#KMMhY|C1HDsBSIiD~1R1vW%FVA*sl4MsIw?-c!iVT`OTHcQC`YDbxB-wV z&eq-$r^0q(J8S-B; z(Km(s_!qPxqq9F5kGl!^?xWNYw*7h`3m&7I{GhSI4%>K{aRU6g(KdtFU14LQ?0<|l zW`{#l?XyTkJSU?ZWXiW5qX!x@gQ_I+7&K*Ospkhq*wC4?YWRs12pc%dY>cjUuUL;8 z-zz_OjB<0_RKkt9>`8aeaBn;*zpyB`Au$-&&pFJ_{6SUi89`B&quyc}K~dJ62K!xY z8Ri_h=t+8e4l8wn>O}}-e2O^wWrq=%oRUiWE}Z{(YO(T+s9bWM?#jx;y&P>rP(A+T z%Ril`^=0|q5-|oL`B~~sm?rwDC@*Z05)sG-G3ZO&8Wv|{<% zQ&d@2oPPCW8O7;WPd*GwK)+Ve!~POE^=ay;D)A0az1kA*@We7eKOOX|6g`Zaj@P8c z^7k*0D}VDem6uKTscE@{#x}(W8)nL!XJ~cp%(P*AV9tbLP1w3{P^nQZ zqIFk>&)YeE6zBaIMTCv3HV3T~?&Jd%&Vs4s(C9*=a&Ea?@=sK-4BwzD_w;I~mU|8n zsynCz7vDaBoIs_S3p4oTPvwjE?x!+lBQnN~rEv0%xXDC2)Ieg`c4NIm z10YQ$W2r=P76^J$6`<;MQ!uqZF_Ln7`r|#jMijZdVn|6DiT5O+U|?wXV0;LrMiIKReTAjz^|RmRr5((v zKMxM(rxcuOwMM<%`Dd$bSLaCsg0wnFn*(X6 zxDCcnW#}T65Ag$R1DO$kBxn^Hy`L#S5Y{+l zrY{UC8)uiQZHEJKiP0k;zDO-Dw`@y1mAZ3o!UCp4rRtoUWO$s4=i(c5P3RQZG~GWp zp;6F?Hz5CZ;Vk9HPyBY_WrK^8XqmTcAlR_X%Tjm?T+QI(lTmfi&GON4a>2zSn!U{n z1g>Up^IB#AYitEqrQ*U=tNePLO2Ng>YfW?UPHatc;k`Am5?uVN#^UyUrCjp@Ew9)1 zvkz8!zlbmk&XwLTB1^qkuL9>B#R)O1P%NgYVlnDD9z+nZ$}1W`5U|QC8kWibnAUg= z01$)XXkL0Vj`*+e$m6s;e(zqBzD)3|_8Lc*1W17NHF!nNv}bc9_$L(qBwC{EL!aAj zy3zSceEJZTxBrG_)TgC$W7ro`$aB8*c|{&qJ0wNFnWa)%UH host.authority.v1.PersonSession 8, // 1: host.authority.v1.IssueRequest.workload:type_name -> host.authority.v1.WorkloadCredential 9, // 2: host.authority.v1.IssueRequest.installation:type_name -> host.authority.v1.InstallationRef 1, // 3: host.authority.v1.IssueRequest.delegation:type_name -> host.authority.v1.DelegationRef - 19, // 4: host.authority.v1.IssueRequest.scopes:type_name -> host.authority.v1.WorkScopeV1 + 21, // 4: host.authority.v1.IssueRequest.scopes:type_name -> host.authority.v1.WorkScopeV1 8, // 5: host.authority.v1.IssueRequest.actor:type_name -> host.authority.v1.WorkloadCredential - 19, // 6: host.authority.v1.DelegateRequest.scopes:type_name -> host.authority.v1.WorkScopeV1 - 19, // 7: host.authority.v1.Delegation.scopes:type_name -> host.authority.v1.WorkScopeV1 + 21, // 6: host.authority.v1.DelegateRequest.scopes:type_name -> host.authority.v1.WorkScopeV1 + 21, // 7: host.authority.v1.Delegation.scopes:type_name -> host.authority.v1.WorkScopeV1 8, // 8: host.authority.v1.GetDelegationRequest.actor:type_name -> host.authority.v1.WorkloadCredential 3, // 9: host.authority.v1.RevokeDelegationResponse.delegation:type_name -> host.authority.v1.Delegation - 20, // 10: host.authority.v1.IssueResponse.context:type_name -> host.authority.v1.WorkContextV1 - 20, // 11: host.authority.v1.CheckResponse.context:type_name -> host.authority.v1.WorkContextV1 + 22, // 10: host.authority.v1.IssueResponse.context:type_name -> host.authority.v1.WorkContextV1 + 22, // 11: host.authority.v1.CheckResponse.context:type_name -> host.authority.v1.WorkContextV1 15, // 12: host.authority.v1.KeysResponse.keys:type_name -> host.authority.v1.PublicKey - 18, // 13: host.authority.v1.ListResourcesResponse.grants:type_name -> host.authority.v1.ResourceGrant - 0, // 14: host.authority.v1.AuthorityService.Issue:input_type -> host.authority.v1.IssueRequest - 11, // 15: host.authority.v1.AuthorityService.Check:input_type -> host.authority.v1.CheckRequest - 13, // 16: host.authority.v1.AuthorityService.Keys:input_type -> host.authority.v1.KeysRequest - 16, // 17: host.authority.v1.AuthorityService.ListResources:input_type -> host.authority.v1.ListResourcesRequest - 2, // 18: host.authority.v1.AuthorityService.Delegate:input_type -> host.authority.v1.DelegateRequest - 5, // 19: host.authority.v1.AuthorityService.RevokeDelegation:input_type -> host.authority.v1.RevokeDelegationRequest - 4, // 20: host.authority.v1.AuthorityService.GetDelegation:input_type -> host.authority.v1.GetDelegationRequest - 10, // 21: host.authority.v1.AuthorityService.Issue:output_type -> host.authority.v1.IssueResponse - 12, // 22: host.authority.v1.AuthorityService.Check:output_type -> host.authority.v1.CheckResponse - 14, // 23: host.authority.v1.AuthorityService.Keys:output_type -> host.authority.v1.KeysResponse - 17, // 24: host.authority.v1.AuthorityService.ListResources:output_type -> host.authority.v1.ListResourcesResponse - 3, // 25: host.authority.v1.AuthorityService.Delegate:output_type -> host.authority.v1.Delegation - 6, // 26: host.authority.v1.AuthorityService.RevokeDelegation:output_type -> host.authority.v1.RevokeDelegationResponse - 3, // 27: host.authority.v1.AuthorityService.GetDelegation:output_type -> host.authority.v1.Delegation - 21, // [21:28] is the sub-list for method output_type - 14, // [14:21] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 20, // 13: host.authority.v1.ListResourcesResponse.grants:type_name -> host.authority.v1.ResourceGrant + 8, // 14: host.authority.v1.AuthorizeTransferRequest.actor:type_name -> host.authority.v1.WorkloadCredential + 0, // 15: host.authority.v1.AuthorityService.Issue:input_type -> host.authority.v1.IssueRequest + 11, // 16: host.authority.v1.AuthorityService.Check:input_type -> host.authority.v1.CheckRequest + 13, // 17: host.authority.v1.AuthorityService.Keys:input_type -> host.authority.v1.KeysRequest + 16, // 18: host.authority.v1.AuthorityService.ListResources:input_type -> host.authority.v1.ListResourcesRequest + 18, // 19: host.authority.v1.AuthorityService.AuthorizeTransfer:input_type -> host.authority.v1.AuthorizeTransferRequest + 2, // 20: host.authority.v1.AuthorityService.Delegate:input_type -> host.authority.v1.DelegateRequest + 5, // 21: host.authority.v1.AuthorityService.RevokeDelegation:input_type -> host.authority.v1.RevokeDelegationRequest + 4, // 22: host.authority.v1.AuthorityService.GetDelegation:input_type -> host.authority.v1.GetDelegationRequest + 10, // 23: host.authority.v1.AuthorityService.Issue:output_type -> host.authority.v1.IssueResponse + 12, // 24: host.authority.v1.AuthorityService.Check:output_type -> host.authority.v1.CheckResponse + 14, // 25: host.authority.v1.AuthorityService.Keys:output_type -> host.authority.v1.KeysResponse + 17, // 26: host.authority.v1.AuthorityService.ListResources:output_type -> host.authority.v1.ListResourcesResponse + 19, // 27: host.authority.v1.AuthorityService.AuthorizeTransfer:output_type -> host.authority.v1.AuthorizeTransferResponse + 3, // 28: host.authority.v1.AuthorityService.Delegate:output_type -> host.authority.v1.Delegation + 6, // 29: host.authority.v1.AuthorityService.RevokeDelegation:output_type -> host.authority.v1.RevokeDelegationResponse + 3, // 30: host.authority.v1.AuthorityService.GetDelegation:output_type -> host.authority.v1.Delegation + 23, // [23:31] is the sub-list for method output_type + 15, // [15:23] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name } func init() { file_host_authority_v1_authority_proto_init() } @@ -1522,7 +1698,7 @@ func file_host_authority_v1_authority_proto_init() { (*IssueRequest_ParentToken)(nil), (*IssueRequest_Delegation)(nil), } - file_host_authority_v1_authority_proto_msgTypes[18].OneofWrappers = []any{ + file_host_authority_v1_authority_proto_msgTypes[20].OneofWrappers = []any{ (*ResourceGrant_All)(nil), (*ResourceGrant_Id)(nil), (*ResourceGrant_Prefix)(nil), @@ -1534,7 +1710,7 @@ func file_host_authority_v1_authority_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_host_authority_v1_authority_proto_rawDesc), len(file_host_authority_v1_authority_proto_rawDesc)), NumEnums: 0, - NumMessages: 19, + NumMessages: 21, NumExtensions: 0, NumServices: 1, }, diff --git a/libraries/authority-client/go/gen/host/authority/v1/authority_grpc.pb.go b/libraries/authority-client/go/gen/host/authority/v1/authority_grpc.pb.go index 51d772ee..e548980e 100644 --- a/libraries/authority-client/go/gen/host/authority/v1/authority_grpc.pb.go +++ b/libraries/authority-client/go/gen/host/authority/v1/authority_grpc.pb.go @@ -26,13 +26,14 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - AuthorityService_Issue_FullMethodName = "/host.authority.v1.AuthorityService/Issue" - AuthorityService_Check_FullMethodName = "/host.authority.v1.AuthorityService/Check" - AuthorityService_Keys_FullMethodName = "/host.authority.v1.AuthorityService/Keys" - AuthorityService_ListResources_FullMethodName = "/host.authority.v1.AuthorityService/ListResources" - AuthorityService_Delegate_FullMethodName = "/host.authority.v1.AuthorityService/Delegate" - AuthorityService_RevokeDelegation_FullMethodName = "/host.authority.v1.AuthorityService/RevokeDelegation" - AuthorityService_GetDelegation_FullMethodName = "/host.authority.v1.AuthorityService/GetDelegation" + AuthorityService_Issue_FullMethodName = "/host.authority.v1.AuthorityService/Issue" + AuthorityService_Check_FullMethodName = "/host.authority.v1.AuthorityService/Check" + AuthorityService_Keys_FullMethodName = "/host.authority.v1.AuthorityService/Keys" + AuthorityService_ListResources_FullMethodName = "/host.authority.v1.AuthorityService/ListResources" + AuthorityService_AuthorizeTransfer_FullMethodName = "/host.authority.v1.AuthorityService/AuthorizeTransfer" + AuthorityService_Delegate_FullMethodName = "/host.authority.v1.AuthorityService/Delegate" + AuthorityService_RevokeDelegation_FullMethodName = "/host.authority.v1.AuthorityService/RevokeDelegation" + AuthorityService_GetDelegation_FullMethodName = "/host.authority.v1.AuthorityService/GetDelegation" ) // AuthorityServiceClient is the client API for AuthorityService service. @@ -57,6 +58,19 @@ type AuthorityServiceClient interface { // rather than carried in the capability. A module maps the answer onto its // own resources; the authority knows them only by id. ListResources(ctx context.Context, in *ListResourcesRequest, opts ...grpc.CallOption) (*ListResourcesResponse, error) + // AuthorizeTransfer decides whether an existing resource may join an + // installed solution's principal. The caller is the kind's one serving + // module, which attests the resource exists and who owns it now + // (from_owner_id), presenting the capability of the person asking: issued + // through the target principal's installation and addressed to the caller. + // It is allowed when the current owner is the organization or a person of + // it (never another solution principal, another organization or an unknown + // owner), the person holds admin on that resource from their own + // organization grants, and the person's current roles on the principal hold + // write on the kind, all read live. An installation acting for itself never + // transfers. The store then records the principal as owner; the original + // creator stays the author. + AuthorizeTransfer(ctx context.Context, in *AuthorizeTransferRequest, opts ...grpc.CallOption) (*AuthorizeTransferResponse, error) // Delegate records a signed-in person's grant to a module (the caller's // bearer is the person); Issue with a delegation acts on it. Delegate(ctx context.Context, in *DelegateRequest, opts ...grpc.CallOption) (*Delegation, error) @@ -117,6 +131,16 @@ func (c *authorityServiceClient) ListResources(ctx context.Context, in *ListReso return out, nil } +func (c *authorityServiceClient) AuthorizeTransfer(ctx context.Context, in *AuthorizeTransferRequest, opts ...grpc.CallOption) (*AuthorizeTransferResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthorizeTransferResponse) + err := c.cc.Invoke(ctx, AuthorityService_AuthorizeTransfer_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *authorityServiceClient) Delegate(ctx context.Context, in *DelegateRequest, opts ...grpc.CallOption) (*Delegation, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Delegation) @@ -169,6 +193,19 @@ type AuthorityServiceServer interface { // rather than carried in the capability. A module maps the answer onto its // own resources; the authority knows them only by id. ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) + // AuthorizeTransfer decides whether an existing resource may join an + // installed solution's principal. The caller is the kind's one serving + // module, which attests the resource exists and who owns it now + // (from_owner_id), presenting the capability of the person asking: issued + // through the target principal's installation and addressed to the caller. + // It is allowed when the current owner is the organization or a person of + // it (never another solution principal, another organization or an unknown + // owner), the person holds admin on that resource from their own + // organization grants, and the person's current roles on the principal hold + // write on the kind, all read live. An installation acting for itself never + // transfers. The store then records the principal as owner; the original + // creator stays the author. + AuthorizeTransfer(context.Context, *AuthorizeTransferRequest) (*AuthorizeTransferResponse, error) // Delegate records a signed-in person's grant to a module (the caller's // bearer is the person); Issue with a delegation acts on it. Delegate(context.Context, *DelegateRequest) (*Delegation, error) @@ -201,6 +238,9 @@ func (UnimplementedAuthorityServiceServer) Keys(context.Context, *KeysRequest) ( func (UnimplementedAuthorityServiceServer) ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) { return nil, status.Error(codes.Unimplemented, "method ListResources not implemented") } +func (UnimplementedAuthorityServiceServer) AuthorizeTransfer(context.Context, *AuthorizeTransferRequest) (*AuthorizeTransferResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AuthorizeTransfer not implemented") +} func (UnimplementedAuthorityServiceServer) Delegate(context.Context, *DelegateRequest) (*Delegation, error) { return nil, status.Error(codes.Unimplemented, "method Delegate not implemented") } @@ -303,6 +343,24 @@ func _AuthorityService_ListResources_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _AuthorityService_AuthorizeTransfer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthorizeTransferRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorityServiceServer).AuthorizeTransfer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorityService_AuthorizeTransfer_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorityServiceServer).AuthorizeTransfer(ctx, req.(*AuthorizeTransferRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _AuthorityService_Delegate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DelegateRequest) if err := dec(in); err != nil { @@ -380,6 +438,10 @@ var AuthorityService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListResources", Handler: _AuthorityService_ListResources_Handler, }, + { + MethodName: "AuthorizeTransfer", + Handler: _AuthorityService_AuthorizeTransfer_Handler, + }, { MethodName: "Delegate", Handler: _AuthorityService_Delegate_Handler, diff --git a/libraries/authority-client/go/gen/host/composition/v1/composition.pb.go b/libraries/authority-client/go/gen/host/composition/v1/composition.pb.go index 606daa51..1ee25c52 100644 --- a/libraries/authority-client/go/gen/host/composition/v1/composition.pb.go +++ b/libraries/authority-client/go/gen/host/composition/v1/composition.pb.go @@ -136,7 +136,12 @@ type AuthorityManifestV1 struct { // one of them installer. Headless-only scopes cannot supply a role grant. // Core's manifest carries no roles, so they travel here until it does. A // module declares none; a module's delivery listing any is refused. - Roles []*RoleV1 `protobuf:"bytes,6,rep,name=roles,proto3" json:"roles,omitempty"` + Roles []*RoleV1 `protobuf:"bytes,6,rep,name=roles,proto3" json:"roles,omitempty"` + // The module checks a resource's owner against an owner-selected scope + // (Claims.AllowsOwned): only an audience declaring it receives one, and a + // delivery withdrawing it refuses the owner-selected capabilities issued + // before. Every solution receiving its roles' scopes declares it. + OwnerAware bool `protobuf:"varint,7,opt,name=owner_aware,json=ownerAware,proto3" json:"owner_aware,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -213,6 +218,13 @@ func (x *AuthorityManifestV1) GetRoles() []*RoleV1 { return nil } +func (x *AuthorityManifestV1) GetOwnerAware() bool { + if x != nil { + return x.OwnerAware + } + return false +} + // MethodPolicyV1 is what a call to one method must hold, as the module's // contract declares it with the authority.hop.v1.policy option. type MethodPolicyV1 struct { @@ -417,7 +429,7 @@ type ModuleManifestV1 struct { Page *PageV1 `protobuf:"bytes,7,opt,name=page,proto3" json:"page,omitempty"` // The roles a solution declares a person may hold on an installation of it, // each within what the solution serves or explicitly consumes through - // invoke/lookup bindings; one is named installer, held by whoever installs it. + // invoke/lookup bindings; one is named installer, held only by whom it is granted to. // Headless-only scopes cannot supply a role grant. A module declares none. Roles []*RoleV1 `protobuf:"bytes,8,rep,name=roles,proto3" json:"roles,omitempty"` unknownFields protoimpl.UnknownFields @@ -769,7 +781,7 @@ const file_host_composition_v1_composition_proto_rawDesc = "" + "\n" + "credential\x18\x03 \x01(\tR\n" + "credential\x12C\n" + - "\tauthority\x18\x04 \x01(\v2%.host.composition.v1.SignedManifestV1R\tauthority\"\xb8\x02\n" + + "\tauthority\x18\x04 \x01(\v2%.host.composition.v1.SignedManifestV1R\tauthority\"\xd9\x02\n" + "\x13AuthorityManifestV1\x12\x16\n" + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1e\n" + "\n" + @@ -778,7 +790,9 @@ const file_host_composition_v1_composition_proto_rawDesc = "" + "\x14core_manifest_digest\x18\x03 \x01(\tR\x12coreManifestDigest\x12=\n" + "\amethods\x18\x04 \x03(\v2#.host.composition.v1.MethodPolicyV1R\amethods\x12E\n" + "\x0escope_ceilings\x18\x05 \x03(\v2\x1e.host.authority.v1.WorkScopeV1R\rscopeCeilings\x121\n" + - "\x05roles\x18\x06 \x03(\v2\x1b.host.composition.v1.RoleV1R\x05roles\"\x92\x01\n" + + "\x05roles\x18\x06 \x03(\v2\x1b.host.composition.v1.RoleV1R\x05roles\x12\x1f\n" + + "\vowner_aware\x18\a \x01(\bR\n" + + "ownerAware\"\x92\x01\n" + "\x0eMethodPolicyV1\x12\x16\n" + "\x06method\x18\x01 \x01(\tR\x06method\x12:\n" + "\brequires\x18\x02 \x03(\v2\x1e.host.authority.v1.WorkScopeV1R\brequires\x12,\n" + diff --git a/libraries/authority-client/go/gen/host/installation/v1/installation.pb.go b/libraries/authority-client/go/gen/host/installation/v1/installation.pb.go index 5c59032b..26529359 100644 --- a/libraries/authority-client/go/gen/host/installation/v1/installation.pb.go +++ b/libraries/authority-client/go/gen/host/installation/v1/installation.pb.go @@ -5,8 +5,10 @@ // source: host/installation/v1/installation.proto // The installation interface: what is installed at an organisation and who may -// use it. Three operations on the host, and one the solution implements, the -// install step the host calls once per installation. +// use it. An installed solution is a principal of its organisation; its roles +// are levels on the data that principal owns, and a grant of a role is the +// access. Operations on the host, and one the solution implements, the install +// step the host calls once per installation. package installationv1 @@ -80,7 +82,7 @@ func (x Installation_Status) Number() protoreflect.EnumNumber { // Deprecated: Use Installation_Status.Descriptor instead. func (Installation_Status) EnumDescriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{4, 0} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{8, 0} } // InstallRequest asks to install one target at one organisation. @@ -156,35 +158,40 @@ func (x *InstallRequest) GetOwnerPersonId() string { return "" } -// GrantToTeamRequest exposes an installation to a team. -type GrantToTeamRequest struct { +// GrantRoleRequest grants one role to exactly one person or team. +type GrantRoleRequest struct { state protoimpl.MessageState `protogen:"open.v1"` + // The organization the installation belongs to; a request naming another + // organization's installation is refused naming the organization, before + // the caller's authority is read. + OrganizationId string `protobuf:"bytes,1,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` // The installation. - InstallationId string `protobuf:"bytes,1,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` - // The team granted the installation's role at its root. - TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` - // The organisation the installation belongs to; a grant is an organisation - // administrator's act, and the host checks the actor against it before it - // reads the installation. Learned from the second implementation (the host). - OrganizationId string `protobuf:"bytes,3,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` + // One of the roles the solution declares. + Role string `protobuf:"bytes,3,opt,name=role,proto3" json:"role,omitempty"` + // The person granted, a member of the organization; exactly one of + // person_id and team_id is set. + PersonId string `protobuf:"bytes,4,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + // The team granted, a team of the organization. + TeamId string `protobuf:"bytes,5,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GrantToTeamRequest) Reset() { - *x = GrantToTeamRequest{} +func (x *GrantRoleRequest) Reset() { + *x = GrantRoleRequest{} mi := &file_host_installation_v1_installation_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GrantToTeamRequest) String() string { +func (x *GrantRoleRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GrantToTeamRequest) ProtoMessage() {} +func (*GrantRoleRequest) ProtoMessage() {} -func (x *GrantToTeamRequest) ProtoReflect() protoreflect.Message { +func (x *GrantRoleRequest) ProtoReflect() protoreflect.Message { mi := &file_host_installation_v1_installation_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -196,32 +203,293 @@ func (x *GrantToTeamRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GrantToTeamRequest.ProtoReflect.Descriptor instead. -func (*GrantToTeamRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GrantRoleRequest.ProtoReflect.Descriptor instead. +func (*GrantRoleRequest) Descriptor() ([]byte, []int) { return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{1} } -func (x *GrantToTeamRequest) GetInstallationId() string { +func (x *GrantRoleRequest) GetOrganizationId() string { + if x != nil { + return x.OrganizationId + } + return "" +} + +func (x *GrantRoleRequest) GetInstallationId() string { + if x != nil { + return x.InstallationId + } + return "" +} + +func (x *GrantRoleRequest) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *GrantRoleRequest) GetPersonId() string { + if x != nil { + return x.PersonId + } + return "" +} + +func (x *GrantRoleRequest) GetTeamId() string { + if x != nil { + return x.TeamId + } + return "" +} + +// RevokeRoleRequest ends one grant, named as GrantRole named it. +type RevokeRoleRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The organization the installation belongs to. + OrganizationId string `protobuf:"bytes,1,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` + // The installation. + InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` + // The role. + Role string `protobuf:"bytes,3,opt,name=role,proto3" json:"role,omitempty"` + // The person; exactly one of person_id and team_id is set. + PersonId string `protobuf:"bytes,4,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + // The team. + TeamId string `protobuf:"bytes,5,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeRoleRequest) Reset() { + *x = RevokeRoleRequest{} + mi := &file_host_installation_v1_installation_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeRoleRequest) ProtoMessage() {} + +func (x *RevokeRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeRoleRequest.ProtoReflect.Descriptor instead. +func (*RevokeRoleRequest) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{2} +} + +func (x *RevokeRoleRequest) GetOrganizationId() string { + if x != nil { + return x.OrganizationId + } + return "" +} + +func (x *RevokeRoleRequest) GetInstallationId() string { if x != nil { return x.InstallationId } return "" } -func (x *GrantToTeamRequest) GetTeamId() string { +func (x *RevokeRoleRequest) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *RevokeRoleRequest) GetPersonId() string { + if x != nil { + return x.PersonId + } + return "" +} + +func (x *RevokeRoleRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } -func (x *GrantToTeamRequest) GetOrganizationId() string { +// ListRoleGrantsRequest names one installation. +type ListRoleGrantsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The organization the installation belongs to. + OrganizationId string `protobuf:"bytes,1,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` + // The installation. + InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRoleGrantsRequest) Reset() { + *x = ListRoleGrantsRequest{} + mi := &file_host_installation_v1_installation_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoleGrantsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleGrantsRequest) ProtoMessage() {} + +func (x *ListRoleGrantsRequest) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleGrantsRequest.ProtoReflect.Descriptor instead. +func (*ListRoleGrantsRequest) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{3} +} + +func (x *ListRoleGrantsRequest) GetOrganizationId() string { if x != nil { return x.OrganizationId } return "" } +func (x *ListRoleGrantsRequest) GetInstallationId() string { + if x != nil { + return x.InstallationId + } + return "" +} + +// ListRoleGrantsResponse is every grant on the installation. +type ListRoleGrantsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The grants. + Grants []*RoleGrant `protobuf:"bytes,1,rep,name=grants,proto3" json:"grants,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRoleGrantsResponse) Reset() { + *x = ListRoleGrantsResponse{} + mi := &file_host_installation_v1_installation_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoleGrantsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleGrantsResponse) ProtoMessage() {} + +func (x *ListRoleGrantsResponse) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleGrantsResponse.ProtoReflect.Descriptor instead. +func (*ListRoleGrantsResponse) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{4} +} + +func (x *ListRoleGrantsResponse) GetGrants() []*RoleGrant { + if x != nil { + return x.Grants + } + return nil +} + +// RoleGrant is one role held by one person or team. +type RoleGrant struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The role. + Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + // The person, when a person holds it. + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + // The team, when a team holds it. + TeamId string `protobuf:"bytes,3,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoleGrant) Reset() { + *x = RoleGrant{} + mi := &file_host_installation_v1_installation_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoleGrant) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoleGrant) ProtoMessage() {} + +func (x *RoleGrant) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoleGrant.ProtoReflect.Descriptor instead. +func (*RoleGrant) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{5} +} + +func (x *RoleGrant) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *RoleGrant) GetPersonId() string { + if x != nil { + return x.PersonId + } + return "" +} + +func (x *RoleGrant) GetTeamId() string { + if x != nil { + return x.TeamId + } + return "" +} + // ListRequest asks for one organisation's installations. type ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -233,7 +501,7 @@ type ListRequest struct { func (x *ListRequest) Reset() { *x = ListRequest{} - mi := &file_host_installation_v1_installation_proto_msgTypes[2] + mi := &file_host_installation_v1_installation_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -245,7 +513,7 @@ func (x *ListRequest) String() string { func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[2] + mi := &file_host_installation_v1_installation_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -258,7 +526,7 @@ func (x *ListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. func (*ListRequest) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{2} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{6} } func (x *ListRequest) GetOrganizationId() string { @@ -279,7 +547,7 @@ type ListResponse struct { func (x *ListResponse) Reset() { *x = ListResponse{} - mi := &file_host_installation_v1_installation_proto_msgTypes[3] + mi := &file_host_installation_v1_installation_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -291,7 +559,7 @@ func (x *ListResponse) String() string { func (*ListResponse) ProtoMessage() {} func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[3] + mi := &file_host_installation_v1_installation_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -304,7 +572,7 @@ func (x *ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. func (*ListResponse) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{3} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{7} } func (x *ListResponse) GetInstallations() []*Installation { @@ -327,24 +595,27 @@ type Installation struct { Status Installation_Status `protobuf:"varint,4,opt,name=status,proto3,enum=host.installation.v1.Installation_Status" json:"status,omitempty"` // Bumped on every change that narrows or widens the installation. Revision uint64 `protobuf:"varint,5,opt,name=revision,proto3" json:"revision,omitempty"` - // The role the installation holds. + // The role the installation holds when it acts for itself (its agent's + // role); no person or team inherits it. Role string `protobuf:"bytes,6,opt,name=role,proto3" json:"role,omitempty"` - // The teams granted it. - TeamIds []string `protobuf:"bytes,7,rep,name=team_ids,json=teamIds,proto3" json:"team_ids,omitempty"` - // The principal the installation acts as for itself. - AgentPrincipalId string `protobuf:"bytes,8,opt,name=agent_principal_id,json=agentPrincipalId,proto3" json:"agent_principal_id,omitempty"` // The namespace the installation's own authored content belongs to (an // agent's source): the host names it, so a module never chooses which source // an installation may run. A module installed by composition is // ".". - Namespace string `protobuf:"bytes,9,opt,name=namespace,proto3" json:"namespace,omitempty"` + Namespace string `protobuf:"bytes,9,opt,name=namespace,proto3" json:"namespace,omitempty"` + // The solution principal: the identity the installation's data is owned by + // and the installation acts as for itself, a stable UUID per organization + // and target, reused by a reinstall. Empty for a module installation. + PrincipalId string `protobuf:"bytes,10,opt,name=principal_id,json=principalId,proto3" json:"principal_id,omitempty"` + // The roles the solution declares, sorted. + Roles []string `protobuf:"bytes,11,rep,name=roles,proto3" json:"roles,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Installation) Reset() { *x = Installation{} - mi := &file_host_installation_v1_installation_proto_msgTypes[4] + mi := &file_host_installation_v1_installation_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -356,7 +627,7 @@ func (x *Installation) String() string { func (*Installation) ProtoMessage() {} func (x *Installation) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[4] + mi := &file_host_installation_v1_installation_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -369,7 +640,7 @@ func (x *Installation) ProtoReflect() protoreflect.Message { // Deprecated: Use Installation.ProtoReflect.Descriptor instead. func (*Installation) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{4} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{8} } func (x *Installation) GetId() string { @@ -414,25 +685,25 @@ func (x *Installation) GetRole() string { return "" } -func (x *Installation) GetTeamIds() []string { +func (x *Installation) GetNamespace() string { if x != nil { - return x.TeamIds + return x.Namespace } - return nil + return "" } -func (x *Installation) GetAgentPrincipalId() string { +func (x *Installation) GetPrincipalId() string { if x != nil { - return x.AgentPrincipalId + return x.PrincipalId } return "" } -func (x *Installation) GetNamespace() string { +func (x *Installation) GetRoles() []string { if x != nil { - return x.Namespace + return x.Roles } - return "" + return nil } // InstallationNotice is what the host hands the solution's install step. @@ -446,7 +717,7 @@ type InstallationNotice struct { func (x *InstallationNotice) Reset() { *x = InstallationNotice{} - mi := &file_host_installation_v1_installation_proto_msgTypes[5] + mi := &file_host_installation_v1_installation_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -458,7 +729,7 @@ func (x *InstallationNotice) String() string { func (*InstallationNotice) ProtoMessage() {} func (x *InstallationNotice) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[5] + mi := &file_host_installation_v1_installation_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -471,7 +742,7 @@ func (x *InstallationNotice) ProtoReflect() protoreflect.Message { // Deprecated: Use InstallationNotice.ProtoReflect.Descriptor instead. func (*InstallationNotice) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{5} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{9} } func (x *InstallationNotice) GetInstallation() *Installation { @@ -490,7 +761,7 @@ type InstallationAck struct { func (x *InstallationAck) Reset() { *x = InstallationAck{} - mi := &file_host_installation_v1_installation_proto_msgTypes[6] + mi := &file_host_installation_v1_installation_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -502,7 +773,7 @@ func (x *InstallationAck) String() string { func (*InstallationAck) ProtoMessage() {} func (x *InstallationAck) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[6] + mi := &file_host_installation_v1_installation_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -515,7 +786,7 @@ func (x *InstallationAck) ProtoReflect() protoreflect.Message { // Deprecated: Use InstallationAck.ProtoReflect.Descriptor instead. func (*InstallationAck) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{6} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{10} } var File_host_installation_v1_installation_proto protoreflect.FileDescriptor @@ -527,36 +798,57 @@ const file_host_installation_v1_installation_proto_rawDesc = "" + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12\x16\n" + "\x06target\x18\x02 \x01(\tR\x06target\x12\x12\n" + "\x04role\x18\x03 \x01(\tR\x04role\x12&\n" + - "\x0fowner_person_id\x18\x04 \x01(\tR\rownerPersonId\"\x7f\n" + - "\x12GrantToTeamRequest\x12'\n" + - "\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\x12\x17\n" + - "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12'\n" + - "\x0forganization_id\x18\x03 \x01(\tR\x0eorganizationId\"6\n" + + "\x0fowner_person_id\x18\x04 \x01(\tR\rownerPersonId\"\xae\x01\n" + + "\x10GrantRoleRequest\x12'\n" + + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12'\n" + + "\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\x12\x12\n" + + "\x04role\x18\x03 \x01(\tR\x04role\x12\x1b\n" + + "\tperson_id\x18\x04 \x01(\tR\bpersonId\x12\x17\n" + + "\ateam_id\x18\x05 \x01(\tR\x06teamId\"\xaf\x01\n" + + "\x11RevokeRoleRequest\x12'\n" + + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12'\n" + + "\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\x12\x12\n" + + "\x04role\x18\x03 \x01(\tR\x04role\x12\x1b\n" + + "\tperson_id\x18\x04 \x01(\tR\bpersonId\x12\x17\n" + + "\ateam_id\x18\x05 \x01(\tR\x06teamId\"i\n" + + "\x15ListRoleGrantsRequest\x12'\n" + + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12'\n" + + "\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\"Q\n" + + "\x16ListRoleGrantsResponse\x127\n" + + "\x06grants\x18\x01 \x03(\v2\x1f.host.installation.v1.RoleGrantR\x06grants\"U\n" + + "\tRoleGrant\x12\x12\n" + + "\x04role\x18\x01 \x01(\tR\x04role\x12\x1b\n" + + "\tperson_id\x18\x02 \x01(\tR\bpersonId\x12\x17\n" + + "\ateam_id\x18\x03 \x01(\tR\x06teamId\"6\n" + "\vListRequest\x12'\n" + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\"X\n" + "\fListResponse\x12H\n" + - "\rinstallations\x18\x01 \x03(\v2\".host.installation.v1.InstallationR\rinstallations\"\x96\x03\n" + + "\rinstallations\x18\x01 \x03(\v2\".host.installation.v1.InstallationR\rinstallations\"\xb0\x03\n" + "\fInstallation\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12'\n" + "\x0forganization_id\x18\x02 \x01(\tR\x0eorganizationId\x12\x16\n" + "\x06target\x18\x03 \x01(\tR\x06target\x12A\n" + "\x06status\x18\x04 \x01(\x0e2).host.installation.v1.Installation.StatusR\x06status\x12\x1a\n" + "\brevision\x18\x05 \x01(\x04R\brevision\x12\x12\n" + - "\x04role\x18\x06 \x01(\tR\x04role\x12\x19\n" + - "\bteam_ids\x18\a \x03(\tR\ateamIds\x12,\n" + - "\x12agent_principal_id\x18\b \x01(\tR\x10agentPrincipalId\x12\x1c\n" + - "\tnamespace\x18\t \x01(\tR\tnamespace\"[\n" + + "\x04role\x18\x06 \x01(\tR\x04role\x12\x1c\n" + + "\tnamespace\x18\t \x01(\tR\tnamespace\x12!\n" + + "\fprincipal_id\x18\n" + + " \x01(\tR\vprincipalId\x12\x14\n" + + "\x05roles\x18\v \x03(\tR\x05roles\"[\n" + "\x06Status\x12\x16\n" + "\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n" + "\x0eSTATUS_PENDING\x10\x01\x12\x11\n" + "\rSTATUS_ACTIVE\x10\x02\x12\x12\n" + - "\x0eSTATUS_REVOKED\x10\x03\"\\\n" + + "\x0eSTATUS_REVOKED\x10\x03J\x04\b\a\x10\bJ\x04\b\b\x10\tR\bteam_idsR\x12agent_principal_id\"\\\n" + "\x12InstallationNotice\x12F\n" + "\finstallation\x18\x01 \x01(\v2\".host.installation.v1.InstallationR\finstallation\"\x11\n" + - "\x0fInstallationAck2\x96\x02\n" + + "\x0fInstallationAck2\xda\x03\n" + "\x13InstallationService\x12S\n" + - "\aInstall\x12$.host.installation.v1.InstallRequest\x1a\".host.installation.v1.Installation\x12[\n" + - "\vGrantToTeam\x12(.host.installation.v1.GrantToTeamRequest\x1a\".host.installation.v1.Installation\x12M\n" + + "\aInstall\x12$.host.installation.v1.InstallRequest\x1a\".host.installation.v1.Installation\x12W\n" + + "\tGrantRole\x12&.host.installation.v1.GrantRoleRequest\x1a\".host.installation.v1.Installation\x12Y\n" + + "\n" + + "RevokeRole\x12'.host.installation.v1.RevokeRoleRequest\x1a\".host.installation.v1.Installation\x12k\n" + + "\x0eListRoleGrants\x12+.host.installation.v1.ListRoleGrantsRequest\x1a,.host.installation.v1.ListRoleGrantsResponse\x12M\n" + "\x04List\x12!.host.installation.v1.ListRequest\x1a\".host.installation.v1.ListResponse2p\n" + "\x12InstallableService\x12Z\n" + "\aInstall\x12(.host.installation.v1.InstallationNotice\x1a%.host.installation.v1.InstallationAckB\xf7\x01\n" + @@ -575,34 +867,43 @@ func file_host_installation_v1_installation_proto_rawDescGZIP() []byte { } var file_host_installation_v1_installation_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_host_installation_v1_installation_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_host_installation_v1_installation_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_host_installation_v1_installation_proto_goTypes = []any{ - (Installation_Status)(0), // 0: host.installation.v1.Installation.Status - (*InstallRequest)(nil), // 1: host.installation.v1.InstallRequest - (*GrantToTeamRequest)(nil), // 2: host.installation.v1.GrantToTeamRequest - (*ListRequest)(nil), // 3: host.installation.v1.ListRequest - (*ListResponse)(nil), // 4: host.installation.v1.ListResponse - (*Installation)(nil), // 5: host.installation.v1.Installation - (*InstallationNotice)(nil), // 6: host.installation.v1.InstallationNotice - (*InstallationAck)(nil), // 7: host.installation.v1.InstallationAck + (Installation_Status)(0), // 0: host.installation.v1.Installation.Status + (*InstallRequest)(nil), // 1: host.installation.v1.InstallRequest + (*GrantRoleRequest)(nil), // 2: host.installation.v1.GrantRoleRequest + (*RevokeRoleRequest)(nil), // 3: host.installation.v1.RevokeRoleRequest + (*ListRoleGrantsRequest)(nil), // 4: host.installation.v1.ListRoleGrantsRequest + (*ListRoleGrantsResponse)(nil), // 5: host.installation.v1.ListRoleGrantsResponse + (*RoleGrant)(nil), // 6: host.installation.v1.RoleGrant + (*ListRequest)(nil), // 7: host.installation.v1.ListRequest + (*ListResponse)(nil), // 8: host.installation.v1.ListResponse + (*Installation)(nil), // 9: host.installation.v1.Installation + (*InstallationNotice)(nil), // 10: host.installation.v1.InstallationNotice + (*InstallationAck)(nil), // 11: host.installation.v1.InstallationAck } var file_host_installation_v1_installation_proto_depIdxs = []int32{ - 5, // 0: host.installation.v1.ListResponse.installations:type_name -> host.installation.v1.Installation - 0, // 1: host.installation.v1.Installation.status:type_name -> host.installation.v1.Installation.Status - 5, // 2: host.installation.v1.InstallationNotice.installation:type_name -> host.installation.v1.Installation - 1, // 3: host.installation.v1.InstallationService.Install:input_type -> host.installation.v1.InstallRequest - 2, // 4: host.installation.v1.InstallationService.GrantToTeam:input_type -> host.installation.v1.GrantToTeamRequest - 3, // 5: host.installation.v1.InstallationService.List:input_type -> host.installation.v1.ListRequest - 6, // 6: host.installation.v1.InstallableService.Install:input_type -> host.installation.v1.InstallationNotice - 5, // 7: host.installation.v1.InstallationService.Install:output_type -> host.installation.v1.Installation - 5, // 8: host.installation.v1.InstallationService.GrantToTeam:output_type -> host.installation.v1.Installation - 4, // 9: host.installation.v1.InstallationService.List:output_type -> host.installation.v1.ListResponse - 7, // 10: host.installation.v1.InstallableService.Install:output_type -> host.installation.v1.InstallationAck - 7, // [7:11] is the sub-list for method output_type - 3, // [3:7] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 6, // 0: host.installation.v1.ListRoleGrantsResponse.grants:type_name -> host.installation.v1.RoleGrant + 9, // 1: host.installation.v1.ListResponse.installations:type_name -> host.installation.v1.Installation + 0, // 2: host.installation.v1.Installation.status:type_name -> host.installation.v1.Installation.Status + 9, // 3: host.installation.v1.InstallationNotice.installation:type_name -> host.installation.v1.Installation + 1, // 4: host.installation.v1.InstallationService.Install:input_type -> host.installation.v1.InstallRequest + 2, // 5: host.installation.v1.InstallationService.GrantRole:input_type -> host.installation.v1.GrantRoleRequest + 3, // 6: host.installation.v1.InstallationService.RevokeRole:input_type -> host.installation.v1.RevokeRoleRequest + 4, // 7: host.installation.v1.InstallationService.ListRoleGrants:input_type -> host.installation.v1.ListRoleGrantsRequest + 7, // 8: host.installation.v1.InstallationService.List:input_type -> host.installation.v1.ListRequest + 10, // 9: host.installation.v1.InstallableService.Install:input_type -> host.installation.v1.InstallationNotice + 9, // 10: host.installation.v1.InstallationService.Install:output_type -> host.installation.v1.Installation + 9, // 11: host.installation.v1.InstallationService.GrantRole:output_type -> host.installation.v1.Installation + 9, // 12: host.installation.v1.InstallationService.RevokeRole:output_type -> host.installation.v1.Installation + 5, // 13: host.installation.v1.InstallationService.ListRoleGrants:output_type -> host.installation.v1.ListRoleGrantsResponse + 8, // 14: host.installation.v1.InstallationService.List:output_type -> host.installation.v1.ListResponse + 11, // 15: host.installation.v1.InstallableService.Install:output_type -> host.installation.v1.InstallationAck + 10, // [10:16] is the sub-list for method output_type + 4, // [4:10] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_host_installation_v1_installation_proto_init() } @@ -616,7 +917,7 @@ func file_host_installation_v1_installation_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_host_installation_v1_installation_proto_rawDesc), len(file_host_installation_v1_installation_proto_rawDesc)), NumEnums: 1, - NumMessages: 7, + NumMessages: 11, NumExtensions: 0, NumServices: 2, }, diff --git a/libraries/authority-client/go/gen/host/installation/v1/installation_grpc.pb.go b/libraries/authority-client/go/gen/host/installation/v1/installation_grpc.pb.go index 8d5c6fea..d6a5f8f9 100644 --- a/libraries/authority-client/go/gen/host/installation/v1/installation_grpc.pb.go +++ b/libraries/authority-client/go/gen/host/installation/v1/installation_grpc.pb.go @@ -5,8 +5,10 @@ // source: host/installation/v1/installation.proto // The installation interface: what is installed at an organisation and who may -// use it. Three operations on the host, and one the solution implements, the -// install step the host calls once per installation. +// use it. An installed solution is a principal of its organisation; its roles +// are levels on the data that principal owns, and a grant of a role is the +// access. Operations on the host, and one the solution implements, the install +// step the host calls once per installation. package installationv1 @@ -24,9 +26,11 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - InstallationService_Install_FullMethodName = "/host.installation.v1.InstallationService/Install" - InstallationService_GrantToTeam_FullMethodName = "/host.installation.v1.InstallationService/GrantToTeam" - InstallationService_List_FullMethodName = "/host.installation.v1.InstallationService/List" + InstallationService_Install_FullMethodName = "/host.installation.v1.InstallationService/Install" + InstallationService_GrantRole_FullMethodName = "/host.installation.v1.InstallationService/GrantRole" + InstallationService_RevokeRole_FullMethodName = "/host.installation.v1.InstallationService/RevokeRole" + InstallationService_ListRoleGrants_FullMethodName = "/host.installation.v1.InstallationService/ListRoleGrants" + InstallationService_List_FullMethodName = "/host.installation.v1.InstallationService/List" ) // InstallationServiceClient is the client API for InstallationService service. @@ -38,8 +42,17 @@ type InstallationServiceClient interface { // Install composes an installation: its agent principal, its root scope and // the row, pending until the target acknowledges its install step. Install(ctx context.Context, in *InstallRequest, opts ...grpc.CallOption) (*Installation, error) - // GrantToTeam exposes an installation to a team under the installation's role. - GrantToTeam(ctx context.Context, in *GrantToTeamRequest, opts ...grpc.CallOption) (*Installation, error) + // GrantRole grants a person or a team one of the solution's roles on the + // installation: the access. Idempotent; it leaves existing capabilities + // valid. An administrator of the installation's organization, or a holder + // of the installer role on that installation, grants. + GrantRole(ctx context.Context, in *GrantRoleRequest, opts ...grpc.CallOption) (*Installation, error) + // RevokeRole ends one grant; every capability its subject holds from it is + // refused at once. Idempotent; the same callers as GrantRole. + RevokeRole(ctx context.Context, in *RevokeRoleRequest, opts ...grpc.CallOption) (*Installation, error) + // ListRoleGrants answers who holds which role on the installation; the same + // callers as GrantRole. + ListRoleGrants(ctx context.Context, in *ListRoleGrantsRequest, opts ...grpc.CallOption) (*ListRoleGrantsResponse, error) // List answers what one organisation has installed: an administrator's read. A // member's view, which installed solutions they may use, is their navigation and // belongs to the entitlement read, not here. @@ -64,10 +77,30 @@ func (c *installationServiceClient) Install(ctx context.Context, in *InstallRequ return out, nil } -func (c *installationServiceClient) GrantToTeam(ctx context.Context, in *GrantToTeamRequest, opts ...grpc.CallOption) (*Installation, error) { +func (c *installationServiceClient) GrantRole(ctx context.Context, in *GrantRoleRequest, opts ...grpc.CallOption) (*Installation, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Installation) - err := c.cc.Invoke(ctx, InstallationService_GrantToTeam_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, InstallationService_GrantRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *installationServiceClient) RevokeRole(ctx context.Context, in *RevokeRoleRequest, opts ...grpc.CallOption) (*Installation, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Installation) + err := c.cc.Invoke(ctx, InstallationService_RevokeRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *installationServiceClient) ListRoleGrants(ctx context.Context, in *ListRoleGrantsRequest, opts ...grpc.CallOption) (*ListRoleGrantsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRoleGrantsResponse) + err := c.cc.Invoke(ctx, InstallationService_ListRoleGrants_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -93,8 +126,17 @@ type InstallationServiceServer interface { // Install composes an installation: its agent principal, its root scope and // the row, pending until the target acknowledges its install step. Install(context.Context, *InstallRequest) (*Installation, error) - // GrantToTeam exposes an installation to a team under the installation's role. - GrantToTeam(context.Context, *GrantToTeamRequest) (*Installation, error) + // GrantRole grants a person or a team one of the solution's roles on the + // installation: the access. Idempotent; it leaves existing capabilities + // valid. An administrator of the installation's organization, or a holder + // of the installer role on that installation, grants. + GrantRole(context.Context, *GrantRoleRequest) (*Installation, error) + // RevokeRole ends one grant; every capability its subject holds from it is + // refused at once. Idempotent; the same callers as GrantRole. + RevokeRole(context.Context, *RevokeRoleRequest) (*Installation, error) + // ListRoleGrants answers who holds which role on the installation; the same + // callers as GrantRole. + ListRoleGrants(context.Context, *ListRoleGrantsRequest) (*ListRoleGrantsResponse, error) // List answers what one organisation has installed: an administrator's read. A // member's view, which installed solutions they may use, is their navigation and // belongs to the entitlement read, not here. @@ -112,8 +154,14 @@ type UnimplementedInstallationServiceServer struct{} func (UnimplementedInstallationServiceServer) Install(context.Context, *InstallRequest) (*Installation, error) { return nil, status.Error(codes.Unimplemented, "method Install not implemented") } -func (UnimplementedInstallationServiceServer) GrantToTeam(context.Context, *GrantToTeamRequest) (*Installation, error) { - return nil, status.Error(codes.Unimplemented, "method GrantToTeam not implemented") +func (UnimplementedInstallationServiceServer) GrantRole(context.Context, *GrantRoleRequest) (*Installation, error) { + return nil, status.Error(codes.Unimplemented, "method GrantRole not implemented") +} +func (UnimplementedInstallationServiceServer) RevokeRole(context.Context, *RevokeRoleRequest) (*Installation, error) { + return nil, status.Error(codes.Unimplemented, "method RevokeRole not implemented") +} +func (UnimplementedInstallationServiceServer) ListRoleGrants(context.Context, *ListRoleGrantsRequest) (*ListRoleGrantsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListRoleGrants not implemented") } func (UnimplementedInstallationServiceServer) List(context.Context, *ListRequest) (*ListResponse, error) { return nil, status.Error(codes.Unimplemented, "method List not implemented") @@ -157,20 +205,56 @@ func _InstallationService_Install_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _InstallationService_GrantToTeam_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GrantToTeamRequest) +func _InstallationService_GrantRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GrantRoleRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(InstallationServiceServer).GrantToTeam(ctx, in) + return srv.(InstallationServiceServer).GrantRole(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: InstallationService_GrantToTeam_FullMethodName, + FullMethod: InstallationService_GrantRole_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(InstallationServiceServer).GrantToTeam(ctx, req.(*GrantToTeamRequest)) + return srv.(InstallationServiceServer).GrantRole(ctx, req.(*GrantRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InstallationService_RevokeRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InstallationServiceServer).RevokeRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InstallationService_RevokeRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InstallationServiceServer).RevokeRole(ctx, req.(*RevokeRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InstallationService_ListRoleGrants_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRoleGrantsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InstallationServiceServer).ListRoleGrants(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InstallationService_ListRoleGrants_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InstallationServiceServer).ListRoleGrants(ctx, req.(*ListRoleGrantsRequest)) } return interceptor(ctx, in, info, handler) } @@ -205,8 +289,16 @@ var InstallationService_ServiceDesc = grpc.ServiceDesc{ Handler: _InstallationService_Install_Handler, }, { - MethodName: "GrantToTeam", - Handler: _InstallationService_GrantToTeam_Handler, + MethodName: "GrantRole", + Handler: _InstallationService_GrantRole_Handler, + }, + { + MethodName: "RevokeRole", + Handler: _InstallationService_RevokeRole_Handler, + }, + { + MethodName: "ListRoleGrants", + Handler: _InstallationService_ListRoleGrants_Handler, }, { MethodName: "List", diff --git a/libraries/authority-client/library.codefly.yaml b/libraries/authority-client/library.codefly.yaml index 2b2f065f..94d3e324 100644 --- a/libraries/authority-client/library.codefly.yaml +++ b/libraries/authority-client/library.codefly.yaml @@ -20,7 +20,7 @@ sources: module: authority service: local endpoint: grpc - contract-digest: sha256:9fcee9f110613a97881cfdfd035de3b82b7b58cac138e1093a47ba4460436d72 + contract-digest: sha256:252acddd2c15e5fa8d0a825af6c817af88af20f4698250909bbe70cd376786b7 services: - GatewayService generated-by: diff --git a/libraries/authority-client/python/authority_client/_gen/host/authority/v1/authority_pb2.py b/libraries/authority-client/python/authority_client/_gen/host/authority/v1/authority_pb2.py index c5c2585e..ee576049 100644 --- a/libraries/authority-client/python/authority_client/_gen/host/authority/v1/authority_pb2.py +++ b/libraries/authority-client/python/authority_client/_gen/host/authority/v1/authority_pb2.py @@ -25,7 +25,7 @@ from authority_client._gen.host.authority.v1 import work_context_pb2 as host_dot_authority_dot_v1_dot_work__context__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!host/authority/v1/authority.proto\x12\x11host.authority.v1\x1a$host/authority/v1/work_context.proto\"\xf3\x04\n\x0cIssueRequest\x12:\n\x06person\x18\x01 \x01(\x0b\x32 .host.authority.v1.PersonSessionH\x00R\x06person\x12\x43\n\x08workload\x18\x02 \x01(\x0b\x32%.host.authority.v1.WorkloadCredentialH\x00R\x08workload\x12H\n\x0cinstallation\x18\x03 \x01(\x0b\x32\".host.authority.v1.InstallationRefH\x00R\x0cinstallation\x12#\n\x0cparent_token\x18\x04 \x01(\tH\x00R\x0bparentToken\x12\x42\n\ndelegation\x18\x0b \x01(\x0b\x32 .host.authority.v1.DelegationRefH\x00R\ndelegation\x12\x1a\n\x08\x61udience\x18\x05 \x01(\tR\x08\x61udience\x12\x36\n\x06scopes\x18\x06 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x06scopes\x12\x1f\n\x0bttl_seconds\x18\x07 \x01(\x03R\nttlSeconds\x12\x30\n\x14operation_binding_id\x18\x08 \x01(\tR\x12operationBindingId\x12\'\n\x0forganization_id\x18\t \x01(\tR\x0eorganizationId\x12;\n\x05\x61\x63tor\x18\n \x01(\x0b\x32%.host.authority.v1.WorkloadCredentialR\x05\x61\x63tor\x12\x1c\n\toperation\x18\x0c \x01(\tR\toperationB\x04\n\x02\x61s\"4\n\rDelegationRef\x12#\n\rdelegation_id\x18\x01 \x01(\tR\x0c\x64\x65legationId\"{\n\x0f\x44\x65legateRequest\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x36\n\x06scopes\x18\x02 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x06scopes\x12\x18\n\x07purpose\x18\x03 \x01(\tR\x07purpose\"\xf6\x01\n\nDelegation\x12#\n\rdelegation_id\x18\x01 \x01(\tR\x0c\x64\x65legationId\x12\'\n\x0forganization_id\x18\x02 \x01(\tR\x0eorganizationId\x12\x18\n\x07grantor\x18\x03 \x01(\tR\x07grantor\x12\x16\n\x06module\x18\x04 \x01(\tR\x06module\x12\x36\n\x06scopes\x18\x05 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x06scopes\x12\x18\n\x07purpose\x18\x06 \x01(\tR\x07purpose\x12\x16\n\x06\x61\x63tive\x18\x07 \x01(\x08R\x06\x61\x63tive\"x\n\x14GetDelegationRequest\x12#\n\rdelegation_id\x18\x01 \x01(\tR\x0c\x64\x65legationId\x12;\n\x05\x61\x63tor\x18\x02 \x01(\x0b\x32%.host.authority.v1.WorkloadCredentialR\x05\x61\x63tor\">\n\x17RevokeDelegationRequest\x12#\n\rdelegation_id\x18\x01 \x01(\tR\x0c\x64\x65legationId\"Y\n\x18RevokeDelegationResponse\x12=\n\ndelegation\x18\x01 \x01(\x0b\x32\x1d.host.authority.v1.DelegationR\ndelegation\"!\n\rPersonSessionJ\x04\x08\x01\x10\x02R\nsession_id\"4\n\x12WorkloadCredential\x12\x1e\n\ncredential\x18\x01 \x01(\tR\ncredential\":\n\x0fInstallationRef\x12\'\n\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\"\x89\x01\n\rIssueResponse\x12\x14\n\x05token\x18\x01 \x01(\tR\x05token\x12&\n\x0f\x65xpires_at_unix\x18\x02 \x01(\x03R\rexpiresAtUnix\x12:\n\x07\x63ontext\x18\x03 \x01(\x0b\x32 .host.authority.v1.WorkContextV1R\x07\x63ontext\"t\n\x0c\x43heckRequest\x12\x14\n\x05token\x18\x01 \x01(\tR\x05token\x12\x1a\n\x08\x61udience\x18\x02 \x01(\tR\x08\x61udience\x12\x16\n\x06method\x18\x03 \x01(\tR\x06method\x12\x1a\n\x08\x61\x64visory\x18\x04 \x01(\x08R\x08\x61\x64visory\"\x9c\x01\n\rCheckResponse\x12:\n\x07\x63ontext\x18\x01 \x01(\x0b\x32 .host.authority.v1.WorkContextV1R\x07\x63ontext\x12\x35\n\x16\x61uthorization_revision\x18\x02 \x01(\x04R\x15\x61uthorizationRevision\x12\x18\n\x07refusal\x18\x03 \x01(\tR\x07refusal\"\r\n\x0bKeysRequest\"X\n\x0cKeysResponse\x12\x16\n\x06issuer\x18\x01 \x01(\tR\x06issuer\x12\x30\n\x04keys\x18\x02 \x03(\x0b\x32\x1c.host.authority.v1.PublicKeyR\x04keys\"_\n\tPublicKey\x12\x15\n\x06key_id\x18\x01 \x01(\tR\x05keyId\x12\x1c\n\talgorithm\x18\x02 \x01(\tR\talgorithm\x12\x1d\n\npublic_key\x18\x03 \x01(\x0cR\tpublicKey\"\x85\x01\n\x14ListResourcesRequest\x12\x14\n\x05token\x18\x01 \x01(\tR\x05token\x12\x1a\n\x08\x61udience\x18\x02 \x01(\tR\x08\x61udience\x12#\n\rresource_kind\x18\x03 \x01(\tR\x0cresourceKind\x12\x16\n\x06\x61\x63tion\x18\x04 \x01(\tR\x06\x61\x63tion\"Q\n\x15ListResourcesResponse\x12\x38\n\x06grants\x18\x01 \x03(\x0b\x32 .host.authority.v1.ResourceGrantR\x06grants\"t\n\rResourceGrant\x12\x12\n\x03\x61ll\x18\x01 \x01(\x08H\x00R\x03\x61ll\x12\x10\n\x02id\x18\x02 \x01(\tH\x00R\x02id\x12\x18\n\x06prefix\x18\x03 \x01(\tH\x00R\x06prefix\x12\x1a\n\x07pattern\x18\x04 \x01(\tH\x00R\x07patternB\x07\n\x05match2\xec\x04\n\x10\x41uthorityService\x12J\n\x05Issue\x12\x1f.host.authority.v1.IssueRequest\x1a .host.authority.v1.IssueResponse\x12J\n\x05\x43heck\x12\x1f.host.authority.v1.CheckRequest\x1a .host.authority.v1.CheckResponse\x12G\n\x04Keys\x12\x1e.host.authority.v1.KeysRequest\x1a\x1f.host.authority.v1.KeysResponse\x12\x62\n\rListResources\x12\'.host.authority.v1.ListResourcesRequest\x1a(.host.authority.v1.ListResourcesResponse\x12M\n\x08\x44\x65legate\x12\".host.authority.v1.DelegateRequest\x1a\x1d.host.authority.v1.Delegation\x12k\n\x10RevokeDelegation\x12*.host.authority.v1.RevokeDelegationRequest\x1a+.host.authority.v1.RevokeDelegationResponse\x12W\n\rGetDelegation\x12\'.host.authority.v1.GetDelegationRequest\x1a\x1d.host.authority.v1.DelegationB/Z-gateway/pkg/gen/host/authority/v1;authorityv1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!host/authority/v1/authority.proto\x12\x11host.authority.v1\x1a$host/authority/v1/work_context.proto\"\xf3\x04\n\x0cIssueRequest\x12:\n\x06person\x18\x01 \x01(\x0b\x32 .host.authority.v1.PersonSessionH\x00R\x06person\x12\x43\n\x08workload\x18\x02 \x01(\x0b\x32%.host.authority.v1.WorkloadCredentialH\x00R\x08workload\x12H\n\x0cinstallation\x18\x03 \x01(\x0b\x32\".host.authority.v1.InstallationRefH\x00R\x0cinstallation\x12#\n\x0cparent_token\x18\x04 \x01(\tH\x00R\x0bparentToken\x12\x42\n\ndelegation\x18\x0b \x01(\x0b\x32 .host.authority.v1.DelegationRefH\x00R\ndelegation\x12\x1a\n\x08\x61udience\x18\x05 \x01(\tR\x08\x61udience\x12\x36\n\x06scopes\x18\x06 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x06scopes\x12\x1f\n\x0bttl_seconds\x18\x07 \x01(\x03R\nttlSeconds\x12\x30\n\x14operation_binding_id\x18\x08 \x01(\tR\x12operationBindingId\x12\'\n\x0forganization_id\x18\t \x01(\tR\x0eorganizationId\x12;\n\x05\x61\x63tor\x18\n \x01(\x0b\x32%.host.authority.v1.WorkloadCredentialR\x05\x61\x63tor\x12\x1c\n\toperation\x18\x0c \x01(\tR\toperationB\x04\n\x02\x61s\"4\n\rDelegationRef\x12#\n\rdelegation_id\x18\x01 \x01(\tR\x0c\x64\x65legationId\"{\n\x0f\x44\x65legateRequest\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x36\n\x06scopes\x18\x02 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x06scopes\x12\x18\n\x07purpose\x18\x03 \x01(\tR\x07purpose\"\xf6\x01\n\nDelegation\x12#\n\rdelegation_id\x18\x01 \x01(\tR\x0c\x64\x65legationId\x12\'\n\x0forganization_id\x18\x02 \x01(\tR\x0eorganizationId\x12\x18\n\x07grantor\x18\x03 \x01(\tR\x07grantor\x12\x16\n\x06module\x18\x04 \x01(\tR\x06module\x12\x36\n\x06scopes\x18\x05 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x06scopes\x12\x18\n\x07purpose\x18\x06 \x01(\tR\x07purpose\x12\x16\n\x06\x61\x63tive\x18\x07 \x01(\x08R\x06\x61\x63tive\"x\n\x14GetDelegationRequest\x12#\n\rdelegation_id\x18\x01 \x01(\tR\x0c\x64\x65legationId\x12;\n\x05\x61\x63tor\x18\x02 \x01(\x0b\x32%.host.authority.v1.WorkloadCredentialR\x05\x61\x63tor\">\n\x17RevokeDelegationRequest\x12#\n\rdelegation_id\x18\x01 \x01(\tR\x0c\x64\x65legationId\"Y\n\x18RevokeDelegationResponse\x12=\n\ndelegation\x18\x01 \x01(\x0b\x32\x1d.host.authority.v1.DelegationR\ndelegation\"!\n\rPersonSessionJ\x04\x08\x01\x10\x02R\nsession_id\"4\n\x12WorkloadCredential\x12\x1e\n\ncredential\x18\x01 \x01(\tR\ncredential\":\n\x0fInstallationRef\x12\'\n\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\"\x89\x01\n\rIssueResponse\x12\x14\n\x05token\x18\x01 \x01(\tR\x05token\x12&\n\x0f\x65xpires_at_unix\x18\x02 \x01(\x03R\rexpiresAtUnix\x12:\n\x07\x63ontext\x18\x03 \x01(\x0b\x32 .host.authority.v1.WorkContextV1R\x07\x63ontext\"t\n\x0c\x43heckRequest\x12\x14\n\x05token\x18\x01 \x01(\tR\x05token\x12\x1a\n\x08\x61udience\x18\x02 \x01(\tR\x08\x61udience\x12\x16\n\x06method\x18\x03 \x01(\tR\x06method\x12\x1a\n\x08\x61\x64visory\x18\x04 \x01(\x08R\x08\x61\x64visory\"\x9c\x01\n\rCheckResponse\x12:\n\x07\x63ontext\x18\x01 \x01(\x0b\x32 .host.authority.v1.WorkContextV1R\x07\x63ontext\x12\x35\n\x16\x61uthorization_revision\x18\x02 \x01(\x04R\x15\x61uthorizationRevision\x12\x18\n\x07refusal\x18\x03 \x01(\tR\x07refusal\"\r\n\x0bKeysRequest\"X\n\x0cKeysResponse\x12\x16\n\x06issuer\x18\x01 \x01(\tR\x06issuer\x12\x30\n\x04keys\x18\x02 \x03(\x0b\x32\x1c.host.authority.v1.PublicKeyR\x04keys\"_\n\tPublicKey\x12\x15\n\x06key_id\x18\x01 \x01(\tR\x05keyId\x12\x1c\n\talgorithm\x18\x02 \x01(\tR\talgorithm\x12\x1d\n\npublic_key\x18\x03 \x01(\x0cR\tpublicKey\"\x85\x01\n\x14ListResourcesRequest\x12\x14\n\x05token\x18\x01 \x01(\tR\x05token\x12\x1a\n\x08\x61udience\x18\x02 \x01(\tR\x08\x61udience\x12#\n\rresource_kind\x18\x03 \x01(\tR\x0cresourceKind\x12\x16\n\x06\x61\x63tion\x18\x04 \x01(\tR\x06\x61\x63tion\"Q\n\x15ListResourcesResponse\x12\x38\n\x06grants\x18\x01 \x03(\x0b\x32 .host.authority.v1.ResourceGrantR\x06grants\"\xff\x01\n\x18\x41uthorizeTransferRequest\x12\x14\n\x05token\x18\x01 \x01(\tR\x05token\x12;\n\x05\x61\x63tor\x18\x02 \x01(\x0b\x32%.host.authority.v1.WorkloadCredentialR\x05\x61\x63tor\x12#\n\rresource_kind\x18\x03 \x01(\tR\x0cresourceKind\x12\x1f\n\x0bresource_id\x18\x04 \x01(\tR\nresourceId\x12\"\n\rfrom_owner_id\x18\x05 \x01(\tR\x0b\x66romOwnerId\x12&\n\x0fto_principal_id\x18\x06 \x01(\tR\rtoPrincipalId\"c\n\x19\x41uthorizeTransferResponse\x12!\n\x0cprincipal_id\x18\x01 \x01(\tR\x0bprincipalId\x12#\n\rauthorized_by\x18\x02 \x01(\tR\x0c\x61uthorizedBy\"\xa0\x01\n\rResourceGrant\x12\x12\n\x03\x61ll\x18\x01 \x01(\x08H\x00R\x03\x61ll\x12\x10\n\x02id\x18\x02 \x01(\tH\x00R\x02id\x12\x18\n\x06prefix\x18\x03 \x01(\tH\x00R\x06prefix\x12\x1a\n\x07pattern\x18\x04 \x01(\tH\x00R\x07pattern\x12*\n\x11resource_owner_id\x18\x05 \x01(\tR\x0fresourceOwnerIdB\x07\n\x05match2\xdc\x05\n\x10\x41uthorityService\x12J\n\x05Issue\x12\x1f.host.authority.v1.IssueRequest\x1a .host.authority.v1.IssueResponse\x12J\n\x05\x43heck\x12\x1f.host.authority.v1.CheckRequest\x1a .host.authority.v1.CheckResponse\x12G\n\x04Keys\x12\x1e.host.authority.v1.KeysRequest\x1a\x1f.host.authority.v1.KeysResponse\x12\x62\n\rListResources\x12\'.host.authority.v1.ListResourcesRequest\x1a(.host.authority.v1.ListResourcesResponse\x12n\n\x11\x41uthorizeTransfer\x12+.host.authority.v1.AuthorizeTransferRequest\x1a,.host.authority.v1.AuthorizeTransferResponse\x12M\n\x08\x44\x65legate\x12\".host.authority.v1.DelegateRequest\x1a\x1d.host.authority.v1.Delegation\x12k\n\x10RevokeDelegation\x12*.host.authority.v1.RevokeDelegationRequest\x1a+.host.authority.v1.RevokeDelegationResponse\x12W\n\rGetDelegation\x12\'.host.authority.v1.GetDelegationRequest\x1a\x1d.host.authority.v1.DelegationB/Z-gateway/pkg/gen/host/authority/v1;authorityv1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -69,8 +69,12 @@ _globals['_LISTRESOURCESREQUEST']._serialized_end=2331 _globals['_LISTRESOURCESRESPONSE']._serialized_start=2333 _globals['_LISTRESOURCESRESPONSE']._serialized_end=2414 - _globals['_RESOURCEGRANT']._serialized_start=2416 - _globals['_RESOURCEGRANT']._serialized_end=2532 - _globals['_AUTHORITYSERVICE']._serialized_start=2535 - _globals['_AUTHORITYSERVICE']._serialized_end=3155 + _globals['_AUTHORIZETRANSFERREQUEST']._serialized_start=2417 + _globals['_AUTHORIZETRANSFERREQUEST']._serialized_end=2672 + _globals['_AUTHORIZETRANSFERRESPONSE']._serialized_start=2674 + _globals['_AUTHORIZETRANSFERRESPONSE']._serialized_end=2773 + _globals['_RESOURCEGRANT']._serialized_start=2776 + _globals['_RESOURCEGRANT']._serialized_end=2936 + _globals['_AUTHORITYSERVICE']._serialized_start=2939 + _globals['_AUTHORITYSERVICE']._serialized_end=3671 # @@protoc_insertion_point(module_scope) diff --git a/libraries/authority-client/python/authority_client/_gen/host/authority/v1/authority_pb2_grpc.py b/libraries/authority-client/python/authority_client/_gen/host/authority/v1/authority_pb2_grpc.py index 43cd9d78..349fb362 100644 --- a/libraries/authority-client/python/authority_client/_gen/host/authority/v1/authority_pb2_grpc.py +++ b/libraries/authority-client/python/authority_client/_gen/host/authority/v1/authority_pb2_grpc.py @@ -55,6 +55,11 @@ def __init__(self, channel): request_serializer=host_dot_authority_dot_v1_dot_authority__pb2.ListResourcesRequest.SerializeToString, response_deserializer=host_dot_authority_dot_v1_dot_authority__pb2.ListResourcesResponse.FromString, _registered_method=True) + self.AuthorizeTransfer = channel.unary_unary( + '/host.authority.v1.AuthorityService/AuthorizeTransfer', + request_serializer=host_dot_authority_dot_v1_dot_authority__pb2.AuthorizeTransferRequest.SerializeToString, + response_deserializer=host_dot_authority_dot_v1_dot_authority__pb2.AuthorizeTransferResponse.FromString, + _registered_method=True) self.Delegate = channel.unary_unary( '/host.authority.v1.AuthorityService/Delegate', request_serializer=host_dot_authority_dot_v1_dot_authority__pb2.DelegateRequest.SerializeToString, @@ -112,6 +117,24 @@ def ListResources(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def AuthorizeTransfer(self, request, context): + """AuthorizeTransfer decides whether an existing resource may join an + installed solution's principal. The caller is the kind's one serving + module, which attests the resource exists and who owns it now + (from_owner_id), presenting the capability of the person asking: issued + through the target principal's installation and addressed to the caller. + It is allowed when the current owner is the organization or a person of + it (never another solution principal, another organization or an unknown + owner), the person holds admin on that resource from their own + organization grants, and the person's current roles on the principal hold + write on the kind, all read live. An installation acting for itself never + transfers. The store then records the principal as owner; the original + creator stays the author. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def Delegate(self, request, context): """Delegate records a signed-in person's grant to a module (the caller's bearer is the person); Issue with a delegation acts on it. @@ -160,6 +183,11 @@ def add_AuthorityServiceServicer_to_server(servicer, server): request_deserializer=host_dot_authority_dot_v1_dot_authority__pb2.ListResourcesRequest.FromString, response_serializer=host_dot_authority_dot_v1_dot_authority__pb2.ListResourcesResponse.SerializeToString, ), + 'AuthorizeTransfer': grpc.unary_unary_rpc_method_handler( + servicer.AuthorizeTransfer, + request_deserializer=host_dot_authority_dot_v1_dot_authority__pb2.AuthorizeTransferRequest.FromString, + response_serializer=host_dot_authority_dot_v1_dot_authority__pb2.AuthorizeTransferResponse.SerializeToString, + ), 'Delegate': grpc.unary_unary_rpc_method_handler( servicer.Delegate, request_deserializer=host_dot_authority_dot_v1_dot_authority__pb2.DelegateRequest.FromString, @@ -295,6 +323,33 @@ def ListResources(request, metadata, _registered_method=True) + @staticmethod + def AuthorizeTransfer(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/host.authority.v1.AuthorityService/AuthorizeTransfer', + host_dot_authority_dot_v1_dot_authority__pb2.AuthorizeTransferRequest.SerializeToString, + host_dot_authority_dot_v1_dot_authority__pb2.AuthorizeTransferResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def Delegate(request, target, diff --git a/libraries/authority-client/python/authority_client/_gen/host/composition/v1/composition_pb2.py b/libraries/authority-client/python/authority_client/_gen/host/composition/v1/composition_pb2.py index fca4ced2..94c22ac4 100644 --- a/libraries/authority-client/python/authority_client/_gen/host/composition/v1/composition_pb2.py +++ b/libraries/authority-client/python/authority_client/_gen/host/composition/v1/composition_pb2.py @@ -25,7 +25,7 @@ from authority_client._gen.host.authority.v1 import work_context_pb2 as host_dot_authority_dot_v1_dot_work__context__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%host/composition/v1/composition.proto\x12\x13host.composition.v1\x1a$host/authority/v1/work_context.proto\"\xff\x01\n\x14\x41pplyManifestRequest\x12\x41\n\x08manifest\x18\x01 \x01(\x0b\x32%.host.composition.v1.ModuleManifestV1R\x08manifest\x12?\n\x07\x63\x61rrier\x18\x02 \x01(\x0b\x32%.host.composition.v1.SignedManifestV1R\x07\x63\x61rrier\x12\x1e\n\ncredential\x18\x03 \x01(\tR\ncredential\x12\x43\n\tauthority\x18\x04 \x01(\x0b\x32%.host.composition.v1.SignedManifestV1R\tauthority\"\xb8\x02\n\x13\x41uthorityManifestV1\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x1e\n\ngeneration\x18\x02 \x01(\x04R\ngeneration\x12\x30\n\x14\x63ore_manifest_digest\x18\x03 \x01(\tR\x12\x63oreManifestDigest\x12=\n\x07methods\x18\x04 \x03(\x0b\x32#.host.composition.v1.MethodPolicyV1R\x07methods\x12\x45\n\x0escope_ceilings\x18\x05 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\rscopeCeilings\x12\x31\n\x05roles\x18\x06 \x03(\x0b\x32\x1b.host.composition.v1.RoleV1R\x05roles\"\x92\x01\n\x0eMethodPolicyV1\x12\x16\n\x06method\x18\x01 \x01(\tR\x06method\x12:\n\x08requires\x18\x02 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x08requires\x12,\n\x12\x61ny_checked_caller\x18\x03 \x01(\x08R\x10\x61nyCheckedCaller\"J\n\x10SignedManifestV1\x12\x1a\n\x08\x64ocument\x18\x01 \x01(\x0cR\x08\x64ocument\x12\x1a\n\x08\x65nvelope\x18\x02 \x01(\x0cR\x08\x65nvelope\"\x80\x01\n\x15\x41pplyManifestResponse\x12\x1c\n\tprincipal\x18\x01 \x01(\tR\tprincipal\x12\x1e\n\ngeneration\x18\x02 \x01(\x04R\ngeneration\x12)\n\x10installation_ids\x18\x03 \x03(\tR\x0finstallationIds\"\xe8\x02\n\x10ModuleManifestV1\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x1c\n\tprincipal\x18\x02 \x01(\tR\tprincipal\x12\x12\n\x04kind\x18\x03 \x01(\tR\x04kind\x12\x1e\n\ngeneration\x18\x04 \x01(\x04R\ngeneration\x12:\n\x08\x62indings\x18\x05 \x03(\x0b\x32\x1e.host.composition.v1.BindingV1R\x08\x62indings\x12J\n\x0eresource_kinds\x18\x06 \x03(\x0b\x32#.host.composition.v1.ResourceKindV1R\rresourceKinds\x12/\n\x04page\x18\x07 \x01(\x0b\x32\x1b.host.composition.v1.PageV1R\x04page\x12\x31\n\x05roles\x18\x08 \x03(\x0b\x32\x1b.host.composition.v1.RoleV1R\x05roles\"T\n\x06RoleV1\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x36\n\x06scopes\x18\x02 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x06scopes\"\x8a\x02\n\tBindingV1\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n\x08\x61udience\x18\x02 \x01(\tR\x08\x61udience\x12\x43\n\rinvoke_scopes\x18\x03 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x0cinvokeScopes\x12G\n\x0fheadless_scopes\x18\x04 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x0eheadlessScopes\x12\x43\n\rlookup_scopes\x18\x05 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x0clookupScopes\">\n\x0eResourceKindV1\x12\x12\n\x04kind\x18\x01 \x01(\tR\x04kind\x12\x18\n\x07\x61\x63tions\x18\x02 \x03(\tR\x07\x61\x63tions\"4\n\x06PageV1\x12\x14\n\x05title\x18\x01 \x01(\tR\x05title\x12\x14\n\x05order\x18\x02 \x01(\x05R\x05order2|\n\x12\x43ompositionService\x12\x66\n\rApplyManifest\x12).host.composition.v1.ApplyManifestRequest\x1a*.host.composition.v1.ApplyManifestResponseB3Z1gateway/pkg/gen/host/composition/v1;compositionv1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%host/composition/v1/composition.proto\x12\x13host.composition.v1\x1a$host/authority/v1/work_context.proto\"\xff\x01\n\x14\x41pplyManifestRequest\x12\x41\n\x08manifest\x18\x01 \x01(\x0b\x32%.host.composition.v1.ModuleManifestV1R\x08manifest\x12?\n\x07\x63\x61rrier\x18\x02 \x01(\x0b\x32%.host.composition.v1.SignedManifestV1R\x07\x63\x61rrier\x12\x1e\n\ncredential\x18\x03 \x01(\tR\ncredential\x12\x43\n\tauthority\x18\x04 \x01(\x0b\x32%.host.composition.v1.SignedManifestV1R\tauthority\"\xd9\x02\n\x13\x41uthorityManifestV1\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x1e\n\ngeneration\x18\x02 \x01(\x04R\ngeneration\x12\x30\n\x14\x63ore_manifest_digest\x18\x03 \x01(\tR\x12\x63oreManifestDigest\x12=\n\x07methods\x18\x04 \x03(\x0b\x32#.host.composition.v1.MethodPolicyV1R\x07methods\x12\x45\n\x0escope_ceilings\x18\x05 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\rscopeCeilings\x12\x31\n\x05roles\x18\x06 \x03(\x0b\x32\x1b.host.composition.v1.RoleV1R\x05roles\x12\x1f\n\x0bowner_aware\x18\x07 \x01(\x08R\nownerAware\"\x92\x01\n\x0eMethodPolicyV1\x12\x16\n\x06method\x18\x01 \x01(\tR\x06method\x12:\n\x08requires\x18\x02 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x08requires\x12,\n\x12\x61ny_checked_caller\x18\x03 \x01(\x08R\x10\x61nyCheckedCaller\"J\n\x10SignedManifestV1\x12\x1a\n\x08\x64ocument\x18\x01 \x01(\x0cR\x08\x64ocument\x12\x1a\n\x08\x65nvelope\x18\x02 \x01(\x0cR\x08\x65nvelope\"\x80\x01\n\x15\x41pplyManifestResponse\x12\x1c\n\tprincipal\x18\x01 \x01(\tR\tprincipal\x12\x1e\n\ngeneration\x18\x02 \x01(\x04R\ngeneration\x12)\n\x10installation_ids\x18\x03 \x03(\tR\x0finstallationIds\"\xe8\x02\n\x10ModuleManifestV1\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x1c\n\tprincipal\x18\x02 \x01(\tR\tprincipal\x12\x12\n\x04kind\x18\x03 \x01(\tR\x04kind\x12\x1e\n\ngeneration\x18\x04 \x01(\x04R\ngeneration\x12:\n\x08\x62indings\x18\x05 \x03(\x0b\x32\x1e.host.composition.v1.BindingV1R\x08\x62indings\x12J\n\x0eresource_kinds\x18\x06 \x03(\x0b\x32#.host.composition.v1.ResourceKindV1R\rresourceKinds\x12/\n\x04page\x18\x07 \x01(\x0b\x32\x1b.host.composition.v1.PageV1R\x04page\x12\x31\n\x05roles\x18\x08 \x03(\x0b\x32\x1b.host.composition.v1.RoleV1R\x05roles\"T\n\x06RoleV1\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x36\n\x06scopes\x18\x02 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x06scopes\"\x8a\x02\n\tBindingV1\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n\x08\x61udience\x18\x02 \x01(\tR\x08\x61udience\x12\x43\n\rinvoke_scopes\x18\x03 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x0cinvokeScopes\x12G\n\x0fheadless_scopes\x18\x04 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x0eheadlessScopes\x12\x43\n\rlookup_scopes\x18\x05 \x03(\x0b\x32\x1e.host.authority.v1.WorkScopeV1R\x0clookupScopes\">\n\x0eResourceKindV1\x12\x12\n\x04kind\x18\x01 \x01(\tR\x04kind\x12\x18\n\x07\x61\x63tions\x18\x02 \x03(\tR\x07\x61\x63tions\"4\n\x06PageV1\x12\x14\n\x05title\x18\x01 \x01(\tR\x05title\x12\x14\n\x05order\x18\x02 \x01(\x05R\x05order2|\n\x12\x43ompositionService\x12\x66\n\rApplyManifest\x12).host.composition.v1.ApplyManifestRequest\x1a*.host.composition.v1.ApplyManifestResponseB3Z1gateway/pkg/gen/host/composition/v1;compositionv1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -36,23 +36,23 @@ _globals['_APPLYMANIFESTREQUEST']._serialized_start=101 _globals['_APPLYMANIFESTREQUEST']._serialized_end=356 _globals['_AUTHORITYMANIFESTV1']._serialized_start=359 - _globals['_AUTHORITYMANIFESTV1']._serialized_end=671 - _globals['_METHODPOLICYV1']._serialized_start=674 - _globals['_METHODPOLICYV1']._serialized_end=820 - _globals['_SIGNEDMANIFESTV1']._serialized_start=822 - _globals['_SIGNEDMANIFESTV1']._serialized_end=896 - _globals['_APPLYMANIFESTRESPONSE']._serialized_start=899 - _globals['_APPLYMANIFESTRESPONSE']._serialized_end=1027 - _globals['_MODULEMANIFESTV1']._serialized_start=1030 - _globals['_MODULEMANIFESTV1']._serialized_end=1390 - _globals['_ROLEV1']._serialized_start=1392 - _globals['_ROLEV1']._serialized_end=1476 - _globals['_BINDINGV1']._serialized_start=1479 - _globals['_BINDINGV1']._serialized_end=1745 - _globals['_RESOURCEKINDV1']._serialized_start=1747 - _globals['_RESOURCEKINDV1']._serialized_end=1809 - _globals['_PAGEV1']._serialized_start=1811 - _globals['_PAGEV1']._serialized_end=1863 - _globals['_COMPOSITIONSERVICE']._serialized_start=1865 - _globals['_COMPOSITIONSERVICE']._serialized_end=1989 + _globals['_AUTHORITYMANIFESTV1']._serialized_end=704 + _globals['_METHODPOLICYV1']._serialized_start=707 + _globals['_METHODPOLICYV1']._serialized_end=853 + _globals['_SIGNEDMANIFESTV1']._serialized_start=855 + _globals['_SIGNEDMANIFESTV1']._serialized_end=929 + _globals['_APPLYMANIFESTRESPONSE']._serialized_start=932 + _globals['_APPLYMANIFESTRESPONSE']._serialized_end=1060 + _globals['_MODULEMANIFESTV1']._serialized_start=1063 + _globals['_MODULEMANIFESTV1']._serialized_end=1423 + _globals['_ROLEV1']._serialized_start=1425 + _globals['_ROLEV1']._serialized_end=1509 + _globals['_BINDINGV1']._serialized_start=1512 + _globals['_BINDINGV1']._serialized_end=1778 + _globals['_RESOURCEKINDV1']._serialized_start=1780 + _globals['_RESOURCEKINDV1']._serialized_end=1842 + _globals['_PAGEV1']._serialized_start=1844 + _globals['_PAGEV1']._serialized_end=1896 + _globals['_COMPOSITIONSERVICE']._serialized_start=1898 + _globals['_COMPOSITIONSERVICE']._serialized_end=2022 # @@protoc_insertion_point(module_scope) diff --git a/libraries/authority-client/python/authority_client/_gen/host/installation/v1/installation_pb2.py b/libraries/authority-client/python/authority_client/_gen/host/installation/v1/installation_pb2.py index 22efabff..b834e4a3 100644 --- a/libraries/authority-client/python/authority_client/_gen/host/installation/v1/installation_pb2.py +++ b/libraries/authority-client/python/authority_client/_gen/host/installation/v1/installation_pb2.py @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'host/installation/v1/installation.proto\x12\x14host.installation.v1\"\x8d\x01\n\x0eInstallRequest\x12\'\n\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12\x16\n\x06target\x18\x02 \x01(\tR\x06target\x12\x12\n\x04role\x18\x03 \x01(\tR\x04role\x12&\n\x0fowner_person_id\x18\x04 \x01(\tR\rownerPersonId\"\x7f\n\x12GrantToTeamRequest\x12\'\n\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\x12\x17\n\x07team_id\x18\x02 \x01(\tR\x06teamId\x12\'\n\x0forganization_id\x18\x03 \x01(\tR\x0eorganizationId\"6\n\x0bListRequest\x12\'\n\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\"X\n\x0cListResponse\x12H\n\rinstallations\x18\x01 \x03(\x0b\x32\".host.installation.v1.InstallationR\rinstallations\"\x96\x03\n\x0cInstallation\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\'\n\x0forganization_id\x18\x02 \x01(\tR\x0eorganizationId\x12\x16\n\x06target\x18\x03 \x01(\tR\x06target\x12\x41\n\x06status\x18\x04 \x01(\x0e\x32).host.installation.v1.Installation.StatusR\x06status\x12\x1a\n\x08revision\x18\x05 \x01(\x04R\x08revision\x12\x12\n\x04role\x18\x06 \x01(\tR\x04role\x12\x19\n\x08team_ids\x18\x07 \x03(\tR\x07teamIds\x12,\n\x12\x61gent_principal_id\x18\x08 \x01(\tR\x10\x61gentPrincipalId\x12\x1c\n\tnamespace\x18\t \x01(\tR\tnamespace\"[\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n\x0eSTATUS_PENDING\x10\x01\x12\x11\n\rSTATUS_ACTIVE\x10\x02\x12\x12\n\x0eSTATUS_REVOKED\x10\x03\"\\\n\x12InstallationNotice\x12\x46\n\x0cinstallation\x18\x01 \x01(\x0b\x32\".host.installation.v1.InstallationR\x0cinstallation\"\x11\n\x0fInstallationAck2\x96\x02\n\x13InstallationService\x12S\n\x07Install\x12$.host.installation.v1.InstallRequest\x1a\".host.installation.v1.Installation\x12[\n\x0bGrantToTeam\x12(.host.installation.v1.GrantToTeamRequest\x1a\".host.installation.v1.Installation\x12M\n\x04List\x12!.host.installation.v1.ListRequest\x1a\".host.installation.v1.ListResponse2p\n\x12InstallableService\x12Z\n\x07Install\x12(.host.installation.v1.InstallationNotice\x1a%.host.installation.v1.InstallationAckB5Z3gateway/pkg/gen/host/installation/v1;installationv1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'host/installation/v1/installation.proto\x12\x14host.installation.v1\"\x8d\x01\n\x0eInstallRequest\x12\'\n\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12\x16\n\x06target\x18\x02 \x01(\tR\x06target\x12\x12\n\x04role\x18\x03 \x01(\tR\x04role\x12&\n\x0fowner_person_id\x18\x04 \x01(\tR\rownerPersonId\"\xae\x01\n\x10GrantRoleRequest\x12\'\n\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12\'\n\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\x12\x12\n\x04role\x18\x03 \x01(\tR\x04role\x12\x1b\n\tperson_id\x18\x04 \x01(\tR\x08personId\x12\x17\n\x07team_id\x18\x05 \x01(\tR\x06teamId\"\xaf\x01\n\x11RevokeRoleRequest\x12\'\n\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12\'\n\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\x12\x12\n\x04role\x18\x03 \x01(\tR\x04role\x12\x1b\n\tperson_id\x18\x04 \x01(\tR\x08personId\x12\x17\n\x07team_id\x18\x05 \x01(\tR\x06teamId\"i\n\x15ListRoleGrantsRequest\x12\'\n\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12\'\n\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\"Q\n\x16ListRoleGrantsResponse\x12\x37\n\x06grants\x18\x01 \x03(\x0b\x32\x1f.host.installation.v1.RoleGrantR\x06grants\"U\n\tRoleGrant\x12\x12\n\x04role\x18\x01 \x01(\tR\x04role\x12\x1b\n\tperson_id\x18\x02 \x01(\tR\x08personId\x12\x17\n\x07team_id\x18\x03 \x01(\tR\x06teamId\"6\n\x0bListRequest\x12\'\n\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\"X\n\x0cListResponse\x12H\n\rinstallations\x18\x01 \x03(\x0b\x32\".host.installation.v1.InstallationR\rinstallations\"\xb0\x03\n\x0cInstallation\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\'\n\x0forganization_id\x18\x02 \x01(\tR\x0eorganizationId\x12\x16\n\x06target\x18\x03 \x01(\tR\x06target\x12\x41\n\x06status\x18\x04 \x01(\x0e\x32).host.installation.v1.Installation.StatusR\x06status\x12\x1a\n\x08revision\x18\x05 \x01(\x04R\x08revision\x12\x12\n\x04role\x18\x06 \x01(\tR\x04role\x12\x1c\n\tnamespace\x18\t \x01(\tR\tnamespace\x12!\n\x0cprincipal_id\x18\n \x01(\tR\x0bprincipalId\x12\x14\n\x05roles\x18\x0b \x03(\tR\x05roles\"[\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n\x0eSTATUS_PENDING\x10\x01\x12\x11\n\rSTATUS_ACTIVE\x10\x02\x12\x12\n\x0eSTATUS_REVOKED\x10\x03J\x04\x08\x07\x10\x08J\x04\x08\x08\x10\tR\x08team_idsR\x12\x61gent_principal_id\"\\\n\x12InstallationNotice\x12\x46\n\x0cinstallation\x18\x01 \x01(\x0b\x32\".host.installation.v1.InstallationR\x0cinstallation\"\x11\n\x0fInstallationAck2\xda\x03\n\x13InstallationService\x12S\n\x07Install\x12$.host.installation.v1.InstallRequest\x1a\".host.installation.v1.Installation\x12W\n\tGrantRole\x12&.host.installation.v1.GrantRoleRequest\x1a\".host.installation.v1.Installation\x12Y\n\nRevokeRole\x12\'.host.installation.v1.RevokeRoleRequest\x1a\".host.installation.v1.Installation\x12k\n\x0eListRoleGrants\x12+.host.installation.v1.ListRoleGrantsRequest\x1a,.host.installation.v1.ListRoleGrantsResponse\x12M\n\x04List\x12!.host.installation.v1.ListRequest\x1a\".host.installation.v1.ListResponse2p\n\x12InstallableService\x12Z\n\x07Install\x12(.host.installation.v1.InstallationNotice\x1a%.host.installation.v1.InstallationAckB5Z3gateway/pkg/gen/host/installation/v1;installationv1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -34,22 +34,30 @@ _globals['DESCRIPTOR']._serialized_options = b'Z3gateway/pkg/gen/host/installation/v1;installationv1' _globals['_INSTALLREQUEST']._serialized_start=66 _globals['_INSTALLREQUEST']._serialized_end=207 - _globals['_GRANTTOTEAMREQUEST']._serialized_start=209 - _globals['_GRANTTOTEAMREQUEST']._serialized_end=336 - _globals['_LISTREQUEST']._serialized_start=338 - _globals['_LISTREQUEST']._serialized_end=392 - _globals['_LISTRESPONSE']._serialized_start=394 - _globals['_LISTRESPONSE']._serialized_end=482 - _globals['_INSTALLATION']._serialized_start=485 - _globals['_INSTALLATION']._serialized_end=891 - _globals['_INSTALLATION_STATUS']._serialized_start=800 - _globals['_INSTALLATION_STATUS']._serialized_end=891 - _globals['_INSTALLATIONNOTICE']._serialized_start=893 - _globals['_INSTALLATIONNOTICE']._serialized_end=985 - _globals['_INSTALLATIONACK']._serialized_start=987 - _globals['_INSTALLATIONACK']._serialized_end=1004 - _globals['_INSTALLATIONSERVICE']._serialized_start=1007 - _globals['_INSTALLATIONSERVICE']._serialized_end=1285 - _globals['_INSTALLABLESERVICE']._serialized_start=1287 - _globals['_INSTALLABLESERVICE']._serialized_end=1399 + _globals['_GRANTROLEREQUEST']._serialized_start=210 + _globals['_GRANTROLEREQUEST']._serialized_end=384 + _globals['_REVOKEROLEREQUEST']._serialized_start=387 + _globals['_REVOKEROLEREQUEST']._serialized_end=562 + _globals['_LISTROLEGRANTSREQUEST']._serialized_start=564 + _globals['_LISTROLEGRANTSREQUEST']._serialized_end=669 + _globals['_LISTROLEGRANTSRESPONSE']._serialized_start=671 + _globals['_LISTROLEGRANTSRESPONSE']._serialized_end=752 + _globals['_ROLEGRANT']._serialized_start=754 + _globals['_ROLEGRANT']._serialized_end=839 + _globals['_LISTREQUEST']._serialized_start=841 + _globals['_LISTREQUEST']._serialized_end=895 + _globals['_LISTRESPONSE']._serialized_start=897 + _globals['_LISTRESPONSE']._serialized_end=985 + _globals['_INSTALLATION']._serialized_start=988 + _globals['_INSTALLATION']._serialized_end=1420 + _globals['_INSTALLATION_STATUS']._serialized_start=1287 + _globals['_INSTALLATION_STATUS']._serialized_end=1378 + _globals['_INSTALLATIONNOTICE']._serialized_start=1422 + _globals['_INSTALLATIONNOTICE']._serialized_end=1514 + _globals['_INSTALLATIONACK']._serialized_start=1516 + _globals['_INSTALLATIONACK']._serialized_end=1533 + _globals['_INSTALLATIONSERVICE']._serialized_start=1536 + _globals['_INSTALLATIONSERVICE']._serialized_end=2010 + _globals['_INSTALLABLESERVICE']._serialized_start=2012 + _globals['_INSTALLABLESERVICE']._serialized_end=2124 # @@protoc_insertion_point(module_scope) diff --git a/libraries/authority-client/python/authority_client/_gen/host/installation/v1/installation_pb2_grpc.py b/libraries/authority-client/python/authority_client/_gen/host/installation/v1/installation_pb2_grpc.py index a00c92f6..0c98693f 100644 --- a/libraries/authority-client/python/authority_client/_gen/host/installation/v1/installation_pb2_grpc.py +++ b/libraries/authority-client/python/authority_client/_gen/host/installation/v1/installation_pb2_grpc.py @@ -40,11 +40,21 @@ def __init__(self, channel): request_serializer=host_dot_installation_dot_v1_dot_installation__pb2.InstallRequest.SerializeToString, response_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.Installation.FromString, _registered_method=True) - self.GrantToTeam = channel.unary_unary( - '/host.installation.v1.InstallationService/GrantToTeam', - request_serializer=host_dot_installation_dot_v1_dot_installation__pb2.GrantToTeamRequest.SerializeToString, + self.GrantRole = channel.unary_unary( + '/host.installation.v1.InstallationService/GrantRole', + request_serializer=host_dot_installation_dot_v1_dot_installation__pb2.GrantRoleRequest.SerializeToString, response_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.Installation.FromString, _registered_method=True) + self.RevokeRole = channel.unary_unary( + '/host.installation.v1.InstallationService/RevokeRole', + request_serializer=host_dot_installation_dot_v1_dot_installation__pb2.RevokeRoleRequest.SerializeToString, + response_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.Installation.FromString, + _registered_method=True) + self.ListRoleGrants = channel.unary_unary( + '/host.installation.v1.InstallationService/ListRoleGrants', + request_serializer=host_dot_installation_dot_v1_dot_installation__pb2.ListRoleGrantsRequest.SerializeToString, + response_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.ListRoleGrantsResponse.FromString, + _registered_method=True) self.List = channel.unary_unary( '/host.installation.v1.InstallationService/List', request_serializer=host_dot_installation_dot_v1_dot_installation__pb2.ListRequest.SerializeToString, @@ -64,8 +74,27 @@ def Install(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def GrantToTeam(self, request, context): - """GrantToTeam exposes an installation to a team under the installation's role. + def GrantRole(self, request, context): + """GrantRole grants a person or a team one of the solution's roles on the + installation: the access. Idempotent; it leaves existing capabilities + valid. An administrator of the installation's organization, or a holder + of the installer role on that installation, grants. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RevokeRole(self, request, context): + """RevokeRole ends one grant; every capability its subject holds from it is + refused at once. Idempotent; the same callers as GrantRole. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListRoleGrants(self, request, context): + """ListRoleGrants answers who holds which role on the installation; the same + callers as GrantRole. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -88,11 +117,21 @@ def add_InstallationServiceServicer_to_server(servicer, server): request_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.InstallRequest.FromString, response_serializer=host_dot_installation_dot_v1_dot_installation__pb2.Installation.SerializeToString, ), - 'GrantToTeam': grpc.unary_unary_rpc_method_handler( - servicer.GrantToTeam, - request_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.GrantToTeamRequest.FromString, + 'GrantRole': grpc.unary_unary_rpc_method_handler( + servicer.GrantRole, + request_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.GrantRoleRequest.FromString, + response_serializer=host_dot_installation_dot_v1_dot_installation__pb2.Installation.SerializeToString, + ), + 'RevokeRole': grpc.unary_unary_rpc_method_handler( + servicer.RevokeRole, + request_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.RevokeRoleRequest.FromString, response_serializer=host_dot_installation_dot_v1_dot_installation__pb2.Installation.SerializeToString, ), + 'ListRoleGrants': grpc.unary_unary_rpc_method_handler( + servicer.ListRoleGrants, + request_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.ListRoleGrantsRequest.FromString, + response_serializer=host_dot_installation_dot_v1_dot_installation__pb2.ListRoleGrantsResponse.SerializeToString, + ), 'List': grpc.unary_unary_rpc_method_handler( servicer.List, request_deserializer=host_dot_installation_dot_v1_dot_installation__pb2.ListRequest.FromString, @@ -138,7 +177,7 @@ def Install(request, _registered_method=True) @staticmethod - def GrantToTeam(request, + def GrantRole(request, target, options=(), channel_credentials=None, @@ -151,8 +190,8 @@ def GrantToTeam(request, return grpc.experimental.unary_unary( request, target, - '/host.installation.v1.InstallationService/GrantToTeam', - host_dot_installation_dot_v1_dot_installation__pb2.GrantToTeamRequest.SerializeToString, + '/host.installation.v1.InstallationService/GrantRole', + host_dot_installation_dot_v1_dot_installation__pb2.GrantRoleRequest.SerializeToString, host_dot_installation_dot_v1_dot_installation__pb2.Installation.FromString, options, channel_credentials, @@ -164,6 +203,60 @@ def GrantToTeam(request, metadata, _registered_method=True) + @staticmethod + def RevokeRole(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/host.installation.v1.InstallationService/RevokeRole', + host_dot_installation_dot_v1_dot_installation__pb2.RevokeRoleRequest.SerializeToString, + host_dot_installation_dot_v1_dot_installation__pb2.Installation.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListRoleGrants(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/host.installation.v1.InstallationService/ListRoleGrants', + host_dot_installation_dot_v1_dot_installation__pb2.ListRoleGrantsRequest.SerializeToString, + host_dot_installation_dot_v1_dot_installation__pb2.ListRoleGrantsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def List(request, target, diff --git a/libraries/authority-client/typescript/src/gen/host/authority/v1/authority_pb.ts b/libraries/authority-client/typescript/src/gen/host/authority/v1/authority_pb.ts index ec333289..42761d18 100644 --- a/libraries/authority-client/typescript/src/gen/host/authority/v1/authority_pb.ts +++ b/libraries/authority-client/typescript/src/gen/host/authority/v1/authority_pb.ts @@ -19,7 +19,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file host/authority/v1/authority.proto. */ export const file_host_authority_v1_authority: GenFile = /*@__PURE__*/ - fileDesc("CiFob3N0L2F1dGhvcml0eS92MS9hdXRob3JpdHkucHJvdG8SEWhvc3QuYXV0aG9yaXR5LnYxIuYDCgxJc3N1ZVJlcXVlc3QSMgoGcGVyc29uGAEgASgLMiAuaG9zdC5hdXRob3JpdHkudjEuUGVyc29uU2Vzc2lvbkgAEjkKCHdvcmtsb2FkGAIgASgLMiUuaG9zdC5hdXRob3JpdHkudjEuV29ya2xvYWRDcmVkZW50aWFsSAASOgoMaW5zdGFsbGF0aW9uGAMgASgLMiIuaG9zdC5hdXRob3JpdHkudjEuSW5zdGFsbGF0aW9uUmVmSAASFgoMcGFyZW50X3Rva2VuGAQgASgJSAASNgoKZGVsZWdhdGlvbhgLIAEoCzIgLmhvc3QuYXV0aG9yaXR5LnYxLkRlbGVnYXRpb25SZWZIABIQCghhdWRpZW5jZRgFIAEoCRIuCgZzY29wZXMYBiADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMRITCgt0dGxfc2Vjb25kcxgHIAEoAxIcChRvcGVyYXRpb25fYmluZGluZ19pZBgIIAEoCRIXCg9vcmdhbml6YXRpb25faWQYCSABKAkSNAoFYWN0b3IYCiABKAsyJS5ob3N0LmF1dGhvcml0eS52MS5Xb3JrbG9hZENyZWRlbnRpYWwSEQoJb3BlcmF0aW9uGAwgASgJQgQKAmFzIiYKDURlbGVnYXRpb25SZWYSFQoNZGVsZWdhdGlvbl9pZBgBIAEoCSJiCg9EZWxlZ2F0ZVJlcXVlc3QSDgoGbW9kdWxlGAEgASgJEi4KBnNjb3BlcxgCIAMoCzIeLmhvc3QuYXV0aG9yaXR5LnYxLldvcmtTY29wZVYxEg8KB3B1cnBvc2UYAyABKAkirgEKCkRlbGVnYXRpb24SFQoNZGVsZWdhdGlvbl9pZBgBIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAiABKAkSDwoHZ3JhbnRvchgDIAEoCRIOCgZtb2R1bGUYBCABKAkSLgoGc2NvcGVzGAUgAygLMh4uaG9zdC5hdXRob3JpdHkudjEuV29ya1Njb3BlVjESDwoHcHVycG9zZRgGIAEoCRIOCgZhY3RpdmUYByABKAgiYwoUR2V0RGVsZWdhdGlvblJlcXVlc3QSFQoNZGVsZWdhdGlvbl9pZBgBIAEoCRI0CgVhY3RvchgCIAEoCzIlLmhvc3QuYXV0aG9yaXR5LnYxLldvcmtsb2FkQ3JlZGVudGlhbCIwChdSZXZva2VEZWxlZ2F0aW9uUmVxdWVzdBIVCg1kZWxlZ2F0aW9uX2lkGAEgASgJIk0KGFJldm9rZURlbGVnYXRpb25SZXNwb25zZRIxCgpkZWxlZ2F0aW9uGAEgASgLMh0uaG9zdC5hdXRob3JpdHkudjEuRGVsZWdhdGlvbiIhCg1QZXJzb25TZXNzaW9uSgQIARACUgpzZXNzaW9uX2lkIigKEldvcmtsb2FkQ3JlZGVudGlhbBISCgpjcmVkZW50aWFsGAEgASgJIioKD0luc3RhbGxhdGlvblJlZhIXCg9pbnN0YWxsYXRpb25faWQYASABKAkiagoNSXNzdWVSZXNwb25zZRINCgV0b2tlbhgBIAEoCRIXCg9leHBpcmVzX2F0X3VuaXgYAiABKAMSMQoHY29udGV4dBgDIAEoCzIgLmhvc3QuYXV0aG9yaXR5LnYxLldvcmtDb250ZXh0VjEiUQoMQ2hlY2tSZXF1ZXN0Eg0KBXRva2VuGAEgASgJEhAKCGF1ZGllbmNlGAIgASgJEg4KBm1ldGhvZBgDIAEoCRIQCghhZHZpc29yeRgEIAEoCCJzCg1DaGVja1Jlc3BvbnNlEjEKB2NvbnRleHQYASABKAsyIC5ob3N0LmF1dGhvcml0eS52MS5Xb3JrQ29udGV4dFYxEh4KFmF1dGhvcml6YXRpb25fcmV2aXNpb24YAiABKAQSDwoHcmVmdXNhbBgDIAEoCSINCgtLZXlzUmVxdWVzdCJKCgxLZXlzUmVzcG9uc2USDgoGaXNzdWVyGAEgASgJEioKBGtleXMYAiADKAsyHC5ob3N0LmF1dGhvcml0eS52MS5QdWJsaWNLZXkiQgoJUHVibGljS2V5Eg4KBmtleV9pZBgBIAEoCRIRCglhbGdvcml0aG0YAiABKAkSEgoKcHVibGljX2tleRgDIAEoDCJeChRMaXN0UmVzb3VyY2VzUmVxdWVzdBINCgV0b2tlbhgBIAEoCRIQCghhdWRpZW5jZRgCIAEoCRIVCg1yZXNvdXJjZV9raW5kGAMgASgJEg4KBmFjdGlvbhgEIAEoCSJJChVMaXN0UmVzb3VyY2VzUmVzcG9uc2USMAoGZ3JhbnRzGAEgAygLMiAuaG9zdC5hdXRob3JpdHkudjEuUmVzb3VyY2VHcmFudCJaCg1SZXNvdXJjZUdyYW50Eg0KA2FsbBgBIAEoCEgAEgwKAmlkGAIgASgJSAASEAoGcHJlZml4GAMgASgJSAASEQoHcGF0dGVybhgEIAEoCUgAQgcKBW1hdGNoMqwFChBBdXRob3JpdHlTZXJ2aWNlEmoKBUlzc3VlEh8uaG9zdC5hdXRob3JpdHkudjEuSXNzdWVSZXF1ZXN0GiAuaG9zdC5hdXRob3JpdHkudjEuSXNzdWVSZXNwb25zZSIegtPkkwIYOgEqIhMvdjEvYXV0aG9yaXR5Omlzc3VlEmoKBUNoZWNrEh8uaG9zdC5hdXRob3JpdHkudjEuQ2hlY2tSZXF1ZXN0GiAuaG9zdC5hdXRob3JpdHkudjEuQ2hlY2tSZXNwb25zZSIegtPkkwIYOgEqIhMvdjEvYXV0aG9yaXR5OmNoZWNrEkcKBEtleXMSHi5ob3N0LmF1dGhvcml0eS52MS5LZXlzUmVxdWVzdBofLmhvc3QuYXV0aG9yaXR5LnYxLktleXNSZXNwb25zZRJiCg1MaXN0UmVzb3VyY2VzEicuaG9zdC5hdXRob3JpdHkudjEuTGlzdFJlc291cmNlc1JlcXVlc3QaKC5ob3N0LmF1dGhvcml0eS52MS5MaXN0UmVzb3VyY2VzUmVzcG9uc2USTQoIRGVsZWdhdGUSIi5ob3N0LmF1dGhvcml0eS52MS5EZWxlZ2F0ZVJlcXVlc3QaHS5ob3N0LmF1dGhvcml0eS52MS5EZWxlZ2F0aW9uEmsKEFJldm9rZURlbGVnYXRpb24SKi5ob3N0LmF1dGhvcml0eS52MS5SZXZva2VEZWxlZ2F0aW9uUmVxdWVzdBorLmhvc3QuYXV0aG9yaXR5LnYxLlJldm9rZURlbGVnYXRpb25SZXNwb25zZRJXCg1HZXREZWxlZ2F0aW9uEicuaG9zdC5hdXRob3JpdHkudjEuR2V0RGVsZWdhdGlvblJlcXVlc3QaHS5ob3N0LmF1dGhvcml0eS52MS5EZWxlZ2F0aW9uQi9aLWdhdGV3YXkvcGtnL2dlbi9ob3N0L2F1dGhvcml0eS92MTthdXRob3JpdHl2MWIGcHJvdG8z", [file_google_api_annotations, file_host_authority_v1_work_context]); + fileDesc("CiFob3N0L2F1dGhvcml0eS92MS9hdXRob3JpdHkucHJvdG8SEWhvc3QuYXV0aG9yaXR5LnYxIuYDCgxJc3N1ZVJlcXVlc3QSMgoGcGVyc29uGAEgASgLMiAuaG9zdC5hdXRob3JpdHkudjEuUGVyc29uU2Vzc2lvbkgAEjkKCHdvcmtsb2FkGAIgASgLMiUuaG9zdC5hdXRob3JpdHkudjEuV29ya2xvYWRDcmVkZW50aWFsSAASOgoMaW5zdGFsbGF0aW9uGAMgASgLMiIuaG9zdC5hdXRob3JpdHkudjEuSW5zdGFsbGF0aW9uUmVmSAASFgoMcGFyZW50X3Rva2VuGAQgASgJSAASNgoKZGVsZWdhdGlvbhgLIAEoCzIgLmhvc3QuYXV0aG9yaXR5LnYxLkRlbGVnYXRpb25SZWZIABIQCghhdWRpZW5jZRgFIAEoCRIuCgZzY29wZXMYBiADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMRITCgt0dGxfc2Vjb25kcxgHIAEoAxIcChRvcGVyYXRpb25fYmluZGluZ19pZBgIIAEoCRIXCg9vcmdhbml6YXRpb25faWQYCSABKAkSNAoFYWN0b3IYCiABKAsyJS5ob3N0LmF1dGhvcml0eS52MS5Xb3JrbG9hZENyZWRlbnRpYWwSEQoJb3BlcmF0aW9uGAwgASgJQgQKAmFzIiYKDURlbGVnYXRpb25SZWYSFQoNZGVsZWdhdGlvbl9pZBgBIAEoCSJiCg9EZWxlZ2F0ZVJlcXVlc3QSDgoGbW9kdWxlGAEgASgJEi4KBnNjb3BlcxgCIAMoCzIeLmhvc3QuYXV0aG9yaXR5LnYxLldvcmtTY29wZVYxEg8KB3B1cnBvc2UYAyABKAkirgEKCkRlbGVnYXRpb24SFQoNZGVsZWdhdGlvbl9pZBgBIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAiABKAkSDwoHZ3JhbnRvchgDIAEoCRIOCgZtb2R1bGUYBCABKAkSLgoGc2NvcGVzGAUgAygLMh4uaG9zdC5hdXRob3JpdHkudjEuV29ya1Njb3BlVjESDwoHcHVycG9zZRgGIAEoCRIOCgZhY3RpdmUYByABKAgiYwoUR2V0RGVsZWdhdGlvblJlcXVlc3QSFQoNZGVsZWdhdGlvbl9pZBgBIAEoCRI0CgVhY3RvchgCIAEoCzIlLmhvc3QuYXV0aG9yaXR5LnYxLldvcmtsb2FkQ3JlZGVudGlhbCIwChdSZXZva2VEZWxlZ2F0aW9uUmVxdWVzdBIVCg1kZWxlZ2F0aW9uX2lkGAEgASgJIk0KGFJldm9rZURlbGVnYXRpb25SZXNwb25zZRIxCgpkZWxlZ2F0aW9uGAEgASgLMh0uaG9zdC5hdXRob3JpdHkudjEuRGVsZWdhdGlvbiIhCg1QZXJzb25TZXNzaW9uSgQIARACUgpzZXNzaW9uX2lkIigKEldvcmtsb2FkQ3JlZGVudGlhbBISCgpjcmVkZW50aWFsGAEgASgJIioKD0luc3RhbGxhdGlvblJlZhIXCg9pbnN0YWxsYXRpb25faWQYASABKAkiagoNSXNzdWVSZXNwb25zZRINCgV0b2tlbhgBIAEoCRIXCg9leHBpcmVzX2F0X3VuaXgYAiABKAMSMQoHY29udGV4dBgDIAEoCzIgLmhvc3QuYXV0aG9yaXR5LnYxLldvcmtDb250ZXh0VjEiUQoMQ2hlY2tSZXF1ZXN0Eg0KBXRva2VuGAEgASgJEhAKCGF1ZGllbmNlGAIgASgJEg4KBm1ldGhvZBgDIAEoCRIQCghhZHZpc29yeRgEIAEoCCJzCg1DaGVja1Jlc3BvbnNlEjEKB2NvbnRleHQYASABKAsyIC5ob3N0LmF1dGhvcml0eS52MS5Xb3JrQ29udGV4dFYxEh4KFmF1dGhvcml6YXRpb25fcmV2aXNpb24YAiABKAQSDwoHcmVmdXNhbBgDIAEoCSINCgtLZXlzUmVxdWVzdCJKCgxLZXlzUmVzcG9uc2USDgoGaXNzdWVyGAEgASgJEioKBGtleXMYAiADKAsyHC5ob3N0LmF1dGhvcml0eS52MS5QdWJsaWNLZXkiQgoJUHVibGljS2V5Eg4KBmtleV9pZBgBIAEoCRIRCglhbGdvcml0aG0YAiABKAkSEgoKcHVibGljX2tleRgDIAEoDCJeChRMaXN0UmVzb3VyY2VzUmVxdWVzdBINCgV0b2tlbhgBIAEoCRIQCghhdWRpZW5jZRgCIAEoCRIVCg1yZXNvdXJjZV9raW5kGAMgASgJEg4KBmFjdGlvbhgEIAEoCSJJChVMaXN0UmVzb3VyY2VzUmVzcG9uc2USMAoGZ3JhbnRzGAEgAygLMiAuaG9zdC5hdXRob3JpdHkudjEuUmVzb3VyY2VHcmFudCK7AQoYQXV0aG9yaXplVHJhbnNmZXJSZXF1ZXN0Eg0KBXRva2VuGAEgASgJEjQKBWFjdG9yGAIgASgLMiUuaG9zdC5hdXRob3JpdHkudjEuV29ya2xvYWRDcmVkZW50aWFsEhUKDXJlc291cmNlX2tpbmQYAyABKAkSEwoLcmVzb3VyY2VfaWQYBCABKAkSFQoNZnJvbV9vd25lcl9pZBgFIAEoCRIXCg90b19wcmluY2lwYWxfaWQYBiABKAkiSAoZQXV0aG9yaXplVHJhbnNmZXJSZXNwb25zZRIUCgxwcmluY2lwYWxfaWQYASABKAkSFQoNYXV0aG9yaXplZF9ieRgCIAEoCSJ1Cg1SZXNvdXJjZUdyYW50Eg0KA2FsbBgBIAEoCEgAEgwKAmlkGAIgASgJSAASEAoGcHJlZml4GAMgASgJSAASEQoHcGF0dGVybhgEIAEoCUgAEhkKEXJlc291cmNlX293bmVyX2lkGAUgASgJQgcKBW1hdGNoMpwGChBBdXRob3JpdHlTZXJ2aWNlEmoKBUlzc3VlEh8uaG9zdC5hdXRob3JpdHkudjEuSXNzdWVSZXF1ZXN0GiAuaG9zdC5hdXRob3JpdHkudjEuSXNzdWVSZXNwb25zZSIegtPkkwIYOgEqIhMvdjEvYXV0aG9yaXR5Omlzc3VlEmoKBUNoZWNrEh8uaG9zdC5hdXRob3JpdHkudjEuQ2hlY2tSZXF1ZXN0GiAuaG9zdC5hdXRob3JpdHkudjEuQ2hlY2tSZXNwb25zZSIegtPkkwIYOgEqIhMvdjEvYXV0aG9yaXR5OmNoZWNrEkcKBEtleXMSHi5ob3N0LmF1dGhvcml0eS52MS5LZXlzUmVxdWVzdBofLmhvc3QuYXV0aG9yaXR5LnYxLktleXNSZXNwb25zZRJiCg1MaXN0UmVzb3VyY2VzEicuaG9zdC5hdXRob3JpdHkudjEuTGlzdFJlc291cmNlc1JlcXVlc3QaKC5ob3N0LmF1dGhvcml0eS52MS5MaXN0UmVzb3VyY2VzUmVzcG9uc2USbgoRQXV0aG9yaXplVHJhbnNmZXISKy5ob3N0LmF1dGhvcml0eS52MS5BdXRob3JpemVUcmFuc2ZlclJlcXVlc3QaLC5ob3N0LmF1dGhvcml0eS52MS5BdXRob3JpemVUcmFuc2ZlclJlc3BvbnNlEk0KCERlbGVnYXRlEiIuaG9zdC5hdXRob3JpdHkudjEuRGVsZWdhdGVSZXF1ZXN0Gh0uaG9zdC5hdXRob3JpdHkudjEuRGVsZWdhdGlvbhJrChBSZXZva2VEZWxlZ2F0aW9uEiouaG9zdC5hdXRob3JpdHkudjEuUmV2b2tlRGVsZWdhdGlvblJlcXVlc3QaKy5ob3N0LmF1dGhvcml0eS52MS5SZXZva2VEZWxlZ2F0aW9uUmVzcG9uc2USVwoNR2V0RGVsZWdhdGlvbhInLmhvc3QuYXV0aG9yaXR5LnYxLkdldERlbGVnYXRpb25SZXF1ZXN0Gh0uaG9zdC5hdXRob3JpdHkudjEuRGVsZWdhdGlvbkIvWi1nYXRld2F5L3BrZy9nZW4vaG9zdC9hdXRob3JpdHkvdjE7YXV0aG9yaXR5djFiBnByb3RvMw", [file_google_api_annotations, file_host_authority_v1_work_context]); /** * IssueRequest names on whose behalf the capability is issued. @@ -648,7 +648,93 @@ export const ListResourcesResponseSchema: GenMessage = /* messageDesc(file_host_authority_v1_authority, 17); /** - * ResourceGrant is one way of naming covered resources. Exactly one is set. + * AuthorizeTransferRequest names one existing resource and where it moves. + * + * @generated from message host.authority.v1.AuthorizeTransferRequest + */ +export type AuthorizeTransferRequest = Message<"host.authority.v1.AuthorizeTransferRequest"> & { + /** + * The capability of the person asking, addressed to the caller. + * + * @generated from field: string token = 1; + */ + token: string; + + /** + * The module serving resource_kind, proving which module it is. + * + * @generated from field: host.authority.v1.WorkloadCredential actor = 2; + */ + actor?: WorkloadCredential; + + /** + * The resource kind, as the caller serves it. + * + * @generated from field: string resource_kind = 3; + */ + resourceKind: string; + + /** + * The resource id, as the caller knows it. + * + * @generated from field: string resource_id = 4; + */ + resourceId: string; + + /** + * The resource's current owner, as the caller records it: the organization + * or a person of it. + * + * @generated from field: string from_owner_id = 5; + */ + fromOwnerId: string; + + /** + * The solution principal the resource joins. + * + * @generated from field: string to_principal_id = 6; + */ + toPrincipalId: string; +}; + +/** + * Describes the message host.authority.v1.AuthorizeTransferRequest. + * Use `create(AuthorizeTransferRequestSchema)` to create a new message. + */ +export const AuthorizeTransferRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_authority_v1_authority, 18); + +/** + * AuthorizeTransferResponse is the decision the store records. + * + * @generated from message host.authority.v1.AuthorizeTransferResponse + */ +export type AuthorizeTransferResponse = Message<"host.authority.v1.AuthorizeTransferResponse"> & { + /** + * The principal that now owns the resource. + * + * @generated from field: string principal_id = 1; + */ + principalId: string; + + /** + * The person who authorized the transfer. + * + * @generated from field: string authorized_by = 2; + */ + authorizedBy: string; +}; + +/** + * Describes the message host.authority.v1.AuthorizeTransferResponse. + * Use `create(AuthorizeTransferResponseSchema)` to create a new message. + */ +export const AuthorizeTransferResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_authority_v1_authority, 19); + +/** + * ResourceGrant is one way of naming covered resources. Exactly one match is + * set; resource_owner_id, when set, qualifies it. * * @generated from message host.authority.v1.ResourceGrant */ @@ -690,6 +776,14 @@ export type ResourceGrant = Message<"host.authority.v1.ResourceGrant"> & { value: string; case: "pattern"; } | { case: undefined; value?: undefined }; + + /** + * The owner the covered resources must have: a resource is covered when + * the match covers its id and it is owned by this principal. + * + * @generated from field: string resource_owner_id = 5; + */ + resourceOwnerId: string; }; /** @@ -697,7 +791,7 @@ export type ResourceGrant = Message<"host.authority.v1.ResourceGrant"> & { * Use `create(ResourceGrantSchema)` to create a new message. */ export const ResourceGrantSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_host_authority_v1_authority, 18); + messageDesc(file_host_authority_v1_authority, 20); /** * AuthorityService is implemented by the host (accounts) and by the local authority. @@ -753,6 +847,27 @@ export const AuthorityService: GenService<{ input: typeof ListResourcesRequestSchema; output: typeof ListResourcesResponseSchema; }, + /** + * AuthorizeTransfer decides whether an existing resource may join an + * installed solution's principal. The caller is the kind's one serving + * module, which attests the resource exists and who owns it now + * (from_owner_id), presenting the capability of the person asking: issued + * through the target principal's installation and addressed to the caller. + * It is allowed when the current owner is the organization or a person of + * it (never another solution principal, another organization or an unknown + * owner), the person holds admin on that resource from their own + * organization grants, and the person's current roles on the principal hold + * write on the kind, all read live. An installation acting for itself never + * transfers. The store then records the principal as owner; the original + * creator stays the author. + * + * @generated from rpc host.authority.v1.AuthorityService.AuthorizeTransfer + */ + authorizeTransfer: { + methodKind: "unary"; + input: typeof AuthorizeTransferRequestSchema; + output: typeof AuthorizeTransferResponseSchema; + }, /** * Delegate records a signed-in person's grant to a module (the caller's * bearer is the person); Issue with a delegation acts on it. diff --git a/libraries/authority-client/typescript/src/gen/host/composition/v1/composition_pb.ts b/libraries/authority-client/typescript/src/gen/host/composition/v1/composition_pb.ts index eab14d07..0eb49499 100644 --- a/libraries/authority-client/typescript/src/gen/host/composition/v1/composition_pb.ts +++ b/libraries/authority-client/typescript/src/gen/host/composition/v1/composition_pb.ts @@ -17,7 +17,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file host/composition/v1/composition.proto. */ export const file_host_composition_v1_composition: GenFile = /*@__PURE__*/ - fileDesc("CiVob3N0L2NvbXBvc2l0aW9uL3YxL2NvbXBvc2l0aW9uLnByb3RvEhNob3N0LmNvbXBvc2l0aW9uLnYxItUBChRBcHBseU1hbmlmZXN0UmVxdWVzdBI3CghtYW5pZmVzdBgBIAEoCzIlLmhvc3QuY29tcG9zaXRpb24udjEuTW9kdWxlTWFuaWZlc3RWMRI2CgdjYXJyaWVyGAIgASgLMiUuaG9zdC5jb21wb3NpdGlvbi52MS5TaWduZWRNYW5pZmVzdFYxEhIKCmNyZWRlbnRpYWwYAyABKAkSOAoJYXV0aG9yaXR5GAQgASgLMiUuaG9zdC5jb21wb3NpdGlvbi52MS5TaWduZWRNYW5pZmVzdFYxIvEBChNBdXRob3JpdHlNYW5pZmVzdFYxEg4KBm1vZHVsZRgBIAEoCRISCgpnZW5lcmF0aW9uGAIgASgEEhwKFGNvcmVfbWFuaWZlc3RfZGlnZXN0GAMgASgJEjQKB21ldGhvZHMYBCADKAsyIy5ob3N0LmNvbXBvc2l0aW9uLnYxLk1ldGhvZFBvbGljeVYxEjYKDnNjb3BlX2NlaWxpbmdzGAUgAygLMh4uaG9zdC5hdXRob3JpdHkudjEuV29ya1Njb3BlVjESKgoFcm9sZXMYBiADKAsyGy5ob3N0LmNvbXBvc2l0aW9uLnYxLlJvbGVWMSJuCg5NZXRob2RQb2xpY3lWMRIOCgZtZXRob2QYASABKAkSMAoIcmVxdWlyZXMYAiADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMRIaChJhbnlfY2hlY2tlZF9jYWxsZXIYAyABKAgiNgoQU2lnbmVkTWFuaWZlc3RWMRIQCghkb2N1bWVudBgBIAEoDBIQCghlbnZlbG9wZRgCIAEoDCJYChVBcHBseU1hbmlmZXN0UmVzcG9uc2USEQoJcHJpbmNpcGFsGAEgASgJEhIKCmdlbmVyYXRpb24YAiABKAQSGAoQaW5zdGFsbGF0aW9uX2lkcxgDIAMoCSKdAgoQTW9kdWxlTWFuaWZlc3RWMRIOCgZtb2R1bGUYASABKAkSEQoJcHJpbmNpcGFsGAIgASgJEgwKBGtpbmQYAyABKAkSEgoKZ2VuZXJhdGlvbhgEIAEoBBIwCghiaW5kaW5ncxgFIAMoCzIeLmhvc3QuY29tcG9zaXRpb24udjEuQmluZGluZ1YxEjsKDnJlc291cmNlX2tpbmRzGAYgAygLMiMuaG9zdC5jb21wb3NpdGlvbi52MS5SZXNvdXJjZUtpbmRWMRIpCgRwYWdlGAcgASgLMhsuaG9zdC5jb21wb3NpdGlvbi52MS5QYWdlVjESKgoFcm9sZXMYCCADKAsyGy5ob3N0LmNvbXBvc2l0aW9uLnYxLlJvbGVWMSJGCgZSb2xlVjESDAoEbmFtZRgBIAEoCRIuCgZzY29wZXMYAiADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMSLQAQoJQmluZGluZ1YxEgoKAmlkGAEgASgJEhAKCGF1ZGllbmNlGAIgASgJEjUKDWludm9rZV9zY29wZXMYAyADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMRI3Cg9oZWFkbGVzc19zY29wZXMYBCADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMRI1Cg1sb29rdXBfc2NvcGVzGAUgAygLMh4uaG9zdC5hdXRob3JpdHkudjEuV29ya1Njb3BlVjEiLwoOUmVzb3VyY2VLaW5kVjESDAoEa2luZBgBIAEoCRIPCgdhY3Rpb25zGAIgAygJIiYKBlBhZ2VWMRINCgV0aXRsZRgBIAEoCRINCgVvcmRlchgCIAEoBTJ8ChJDb21wb3NpdGlvblNlcnZpY2USZgoNQXBwbHlNYW5pZmVzdBIpLmhvc3QuY29tcG9zaXRpb24udjEuQXBwbHlNYW5pZmVzdFJlcXVlc3QaKi5ob3N0LmNvbXBvc2l0aW9uLnYxLkFwcGx5TWFuaWZlc3RSZXNwb25zZUIzWjFnYXRld2F5L3BrZy9nZW4vaG9zdC9jb21wb3NpdGlvbi92MTtjb21wb3NpdGlvbnYxYgZwcm90bzM", [file_host_authority_v1_work_context]); + fileDesc("CiVob3N0L2NvbXBvc2l0aW9uL3YxL2NvbXBvc2l0aW9uLnByb3RvEhNob3N0LmNvbXBvc2l0aW9uLnYxItUBChRBcHBseU1hbmlmZXN0UmVxdWVzdBI3CghtYW5pZmVzdBgBIAEoCzIlLmhvc3QuY29tcG9zaXRpb24udjEuTW9kdWxlTWFuaWZlc3RWMRI2CgdjYXJyaWVyGAIgASgLMiUuaG9zdC5jb21wb3NpdGlvbi52MS5TaWduZWRNYW5pZmVzdFYxEhIKCmNyZWRlbnRpYWwYAyABKAkSOAoJYXV0aG9yaXR5GAQgASgLMiUuaG9zdC5jb21wb3NpdGlvbi52MS5TaWduZWRNYW5pZmVzdFYxIoYCChNBdXRob3JpdHlNYW5pZmVzdFYxEg4KBm1vZHVsZRgBIAEoCRISCgpnZW5lcmF0aW9uGAIgASgEEhwKFGNvcmVfbWFuaWZlc3RfZGlnZXN0GAMgASgJEjQKB21ldGhvZHMYBCADKAsyIy5ob3N0LmNvbXBvc2l0aW9uLnYxLk1ldGhvZFBvbGljeVYxEjYKDnNjb3BlX2NlaWxpbmdzGAUgAygLMh4uaG9zdC5hdXRob3JpdHkudjEuV29ya1Njb3BlVjESKgoFcm9sZXMYBiADKAsyGy5ob3N0LmNvbXBvc2l0aW9uLnYxLlJvbGVWMRITCgtvd25lcl9hd2FyZRgHIAEoCCJuCg5NZXRob2RQb2xpY3lWMRIOCgZtZXRob2QYASABKAkSMAoIcmVxdWlyZXMYAiADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMRIaChJhbnlfY2hlY2tlZF9jYWxsZXIYAyABKAgiNgoQU2lnbmVkTWFuaWZlc3RWMRIQCghkb2N1bWVudBgBIAEoDBIQCghlbnZlbG9wZRgCIAEoDCJYChVBcHBseU1hbmlmZXN0UmVzcG9uc2USEQoJcHJpbmNpcGFsGAEgASgJEhIKCmdlbmVyYXRpb24YAiABKAQSGAoQaW5zdGFsbGF0aW9uX2lkcxgDIAMoCSKdAgoQTW9kdWxlTWFuaWZlc3RWMRIOCgZtb2R1bGUYASABKAkSEQoJcHJpbmNpcGFsGAIgASgJEgwKBGtpbmQYAyABKAkSEgoKZ2VuZXJhdGlvbhgEIAEoBBIwCghiaW5kaW5ncxgFIAMoCzIeLmhvc3QuY29tcG9zaXRpb24udjEuQmluZGluZ1YxEjsKDnJlc291cmNlX2tpbmRzGAYgAygLMiMuaG9zdC5jb21wb3NpdGlvbi52MS5SZXNvdXJjZUtpbmRWMRIpCgRwYWdlGAcgASgLMhsuaG9zdC5jb21wb3NpdGlvbi52MS5QYWdlVjESKgoFcm9sZXMYCCADKAsyGy5ob3N0LmNvbXBvc2l0aW9uLnYxLlJvbGVWMSJGCgZSb2xlVjESDAoEbmFtZRgBIAEoCRIuCgZzY29wZXMYAiADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMSLQAQoJQmluZGluZ1YxEgoKAmlkGAEgASgJEhAKCGF1ZGllbmNlGAIgASgJEjUKDWludm9rZV9zY29wZXMYAyADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMRI3Cg9oZWFkbGVzc19zY29wZXMYBCADKAsyHi5ob3N0LmF1dGhvcml0eS52MS5Xb3JrU2NvcGVWMRI1Cg1sb29rdXBfc2NvcGVzGAUgAygLMh4uaG9zdC5hdXRob3JpdHkudjEuV29ya1Njb3BlVjEiLwoOUmVzb3VyY2VLaW5kVjESDAoEa2luZBgBIAEoCRIPCgdhY3Rpb25zGAIgAygJIiYKBlBhZ2VWMRINCgV0aXRsZRgBIAEoCRINCgVvcmRlchgCIAEoBTJ8ChJDb21wb3NpdGlvblNlcnZpY2USZgoNQXBwbHlNYW5pZmVzdBIpLmhvc3QuY29tcG9zaXRpb24udjEuQXBwbHlNYW5pZmVzdFJlcXVlc3QaKi5ob3N0LmNvbXBvc2l0aW9uLnYxLkFwcGx5TWFuaWZlc3RSZXNwb25zZUIzWjFnYXRld2F5L3BrZy9nZW4vaG9zdC9jb21wb3NpdGlvbi92MTtjb21wb3NpdGlvbnYxYgZwcm90bzM", [file_host_authority_v1_work_context]); /** * ApplyManifestRequest carries the manifest. @@ -129,6 +129,16 @@ export type AuthorityManifestV1 = Message<"host.composition.v1.AuthorityManifest * @generated from field: repeated host.composition.v1.RoleV1 roles = 6; */ roles: RoleV1[]; + + /** + * The module checks a resource's owner against an owner-selected scope + * (Claims.AllowsOwned): only an audience declaring it receives one, and a + * delivery withdrawing it refuses the owner-selected capabilities issued + * before. Every solution receiving its roles' scopes declares it. + * + * @generated from field: bool owner_aware = 7; + */ + ownerAware: boolean; }; /** @@ -294,7 +304,7 @@ export type ModuleManifestV1 = Message<"host.composition.v1.ModuleManifestV1"> & /** * The roles a solution declares a person may hold on an installation of it, * each within what the solution serves or explicitly consumes through - * invoke/lookup bindings; one is named installer, held by whoever installs it. + * invoke/lookup bindings; one is named installer, held only by whom it is granted to. * Headless-only scopes cannot supply a role grant. A module declares none. * * @generated from field: repeated host.composition.v1.RoleV1 roles = 8; diff --git a/libraries/authority-client/typescript/src/gen/host/installation/v1/installation_pb.ts b/libraries/authority-client/typescript/src/gen/host/installation/v1/installation_pb.ts index 0717f0e5..92cc19ae 100644 --- a/libraries/authority-client/typescript/src/gen/host/installation/v1/installation_pb.ts +++ b/libraries/authority-client/typescript/src/gen/host/installation/v1/installation_pb.ts @@ -3,8 +3,10 @@ /* eslint-disable */ // The installation interface: what is installed at an organisation and who may -// use it. Three operations on the host, and one the solution implements, the -// install step the host calls once per installation. +// use it. An installed solution is a principal of its organisation; its roles +// are levels on the data that principal owns, and a grant of a role is the +// access. Operations on the host, and one the solution implements, the install +// step the host calls once per installation. import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; @@ -14,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file host/installation/v1/installation.proto. */ export const file_host_installation_v1_installation: GenFile = /*@__PURE__*/ - fileDesc("Cidob3N0L2luc3RhbGxhdGlvbi92MS9pbnN0YWxsYXRpb24ucHJvdG8SFGhvc3QuaW5zdGFsbGF0aW9uLnYxImAKDkluc3RhbGxSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIOCgZ0YXJnZXQYAiABKAkSDAoEcm9sZRgDIAEoCRIXCg9vd25lcl9wZXJzb25faWQYBCABKAkiVwoSR3JhbnRUb1RlYW1SZXF1ZXN0EhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRIPCgd0ZWFtX2lkGAIgASgJEhcKD29yZ2FuaXphdGlvbl9pZBgDIAEoCSImCgtMaXN0UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkiSQoMTGlzdFJlc3BvbnNlEjkKDWluc3RhbGxhdGlvbnMYASADKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24ivAIKDEluc3RhbGxhdGlvbhIKCgJpZBgBIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAiABKAkSDgoGdGFyZ2V0GAMgASgJEjkKBnN0YXR1cxgEIAEoDjIpLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbi5TdGF0dXMSEAoIcmV2aXNpb24YBSABKAQSDAoEcm9sZRgGIAEoCRIQCgh0ZWFtX2lkcxgHIAMoCRIaChJhZ2VudF9wcmluY2lwYWxfaWQYCCABKAkSEQoJbmFtZXNwYWNlGAkgASgJIlsKBlN0YXR1cxIWChJTVEFUVVNfVU5TUEVDSUZJRUQQABISCg5TVEFUVVNfUEVORElORxABEhEKDVNUQVRVU19BQ1RJVkUQAhISCg5TVEFUVVNfUkVWT0tFRBADIk4KEkluc3RhbGxhdGlvbk5vdGljZRI4CgxpbnN0YWxsYXRpb24YASABKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24iEQoPSW5zdGFsbGF0aW9uQWNrMpYCChNJbnN0YWxsYXRpb25TZXJ2aWNlElMKB0luc3RhbGwSJC5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsUmVxdWVzdBoiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhJbCgtHcmFudFRvVGVhbRIoLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkdyYW50VG9UZWFtUmVxdWVzdBoiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhJNCgRMaXN0EiEuaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdFJlcXVlc3QaIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0UmVzcG9uc2UycAoSSW5zdGFsbGFibGVTZXJ2aWNlEloKB0luc3RhbGwSKC5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb25Ob3RpY2UaJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb25BY2tCNVozZ2F0ZXdheS9wa2cvZ2VuL2hvc3QvaW5zdGFsbGF0aW9uL3YxO2luc3RhbGxhdGlvbnYxYgZwcm90bzM"); + fileDesc("Cidob3N0L2luc3RhbGxhdGlvbi92MS9pbnN0YWxsYXRpb24ucHJvdG8SFGhvc3QuaW5zdGFsbGF0aW9uLnYxImAKDkluc3RhbGxSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIOCgZ0YXJnZXQYAiABKAkSDAoEcm9sZRgDIAEoCRIXCg9vd25lcl9wZXJzb25faWQYBCABKAkidgoQR3JhbnRSb2xlUmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkSFwoPaW5zdGFsbGF0aW9uX2lkGAIgASgJEgwKBHJvbGUYAyABKAkSEQoJcGVyc29uX2lkGAQgASgJEg8KB3RlYW1faWQYBSABKAkidwoRUmV2b2tlUm9sZVJlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCRIMCgRyb2xlGAMgASgJEhEKCXBlcnNvbl9pZBgEIAEoCRIPCgd0ZWFtX2lkGAUgASgJIkkKFUxpc3RSb2xlR3JhbnRzUmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkSFwoPaW5zdGFsbGF0aW9uX2lkGAIgASgJIkkKFkxpc3RSb2xlR3JhbnRzUmVzcG9uc2USLwoGZ3JhbnRzGAEgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuUm9sZUdyYW50Ij0KCVJvbGVHcmFudBIMCgRyb2xlGAEgASgJEhEKCXBlcnNvbl9pZBgCIAEoCRIPCgd0ZWFtX2lkGAMgASgJIiYKC0xpc3RSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCSJJCgxMaXN0UmVzcG9uc2USOQoNaW5zdGFsbGF0aW9ucxgBIAMoCzIiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbiLdAgoMSW5zdGFsbGF0aW9uEgoKAmlkGAEgASgJEhcKD29yZ2FuaXphdGlvbl9pZBgCIAEoCRIOCgZ0YXJnZXQYAyABKAkSOQoGc3RhdHVzGAQgASgOMikuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uLlN0YXR1cxIQCghyZXZpc2lvbhgFIAEoBBIMCgRyb2xlGAYgASgJEhEKCW5hbWVzcGFjZRgJIAEoCRIUCgxwcmluY2lwYWxfaWQYCiABKAkSDQoFcm9sZXMYCyADKAkiWwoGU3RhdHVzEhYKElNUQVRVU19VTlNQRUNJRklFRBAAEhIKDlNUQVRVU19QRU5ESU5HEAESEQoNU1RBVFVTX0FDVElWRRACEhIKDlNUQVRVU19SRVZPS0VEEANKBAgHEAhKBAgIEAlSCHRlYW1faWRzUhJhZ2VudF9wcmluY2lwYWxfaWQiTgoSSW5zdGFsbGF0aW9uTm90aWNlEjgKDGluc3RhbGxhdGlvbhgBIAEoCzIiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbiIRCg9JbnN0YWxsYXRpb25BY2sy2gMKE0luc3RhbGxhdGlvblNlcnZpY2USUwoHSW5zdGFsbBIkLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxSZXF1ZXN0GiIuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uElcKCUdyYW50Um9sZRImLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkdyYW50Um9sZVJlcXVlc3QaIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24SWQoKUmV2b2tlUm9sZRInLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJldm9rZVJvbGVSZXF1ZXN0GiIuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uEmsKDkxpc3RSb2xlR3JhbnRzEisuaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdFJvbGVHcmFudHNSZXF1ZXN0GiwuaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdFJvbGVHcmFudHNSZXNwb25zZRJNCgRMaXN0EiEuaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdFJlcXVlc3QaIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0UmVzcG9uc2UycAoSSW5zdGFsbGFibGVTZXJ2aWNlEloKB0luc3RhbGwSKC5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb25Ob3RpY2UaJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb25BY2tCNVozZ2F0ZXdheS9wa2cvZ2VuL2hvc3QvaW5zdGFsbGF0aW9uL3YxO2luc3RhbGxhdGlvbnYxYgZwcm90bzM"); /** * InstallRequest asks to install one target at one organisation. @@ -59,41 +61,189 @@ export const InstallRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_host_installation_v1_installation, 0); /** - * GrantToTeamRequest exposes an installation to a team. + * GrantRoleRequest grants one role to exactly one person or team. * - * @generated from message host.installation.v1.GrantToTeamRequest + * @generated from message host.installation.v1.GrantRoleRequest */ -export type GrantToTeamRequest = Message<"host.installation.v1.GrantToTeamRequest"> & { +export type GrantRoleRequest = Message<"host.installation.v1.GrantRoleRequest"> & { + /** + * The organization the installation belongs to; a request naming another + * organization's installation is refused naming the organization, before + * the caller's authority is read. + * + * @generated from field: string organization_id = 1; + */ + organizationId: string; + /** * The installation. * - * @generated from field: string installation_id = 1; + * @generated from field: string installation_id = 2; */ installationId: string; /** - * The team granted the installation's role at its root. + * One of the roles the solution declares. * - * @generated from field: string team_id = 2; + * @generated from field: string role = 3; + */ + role: string; + + /** + * The person granted, a member of the organization; exactly one of + * person_id and team_id is set. + * + * @generated from field: string person_id = 4; + */ + personId: string; + + /** + * The team granted, a team of the organization. + * + * @generated from field: string team_id = 5; */ teamId: string; +}; +/** + * Describes the message host.installation.v1.GrantRoleRequest. + * Use `create(GrantRoleRequestSchema)` to create a new message. + */ +export const GrantRoleRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 1); + +/** + * RevokeRoleRequest ends one grant, named as GrantRole named it. + * + * @generated from message host.installation.v1.RevokeRoleRequest + */ +export type RevokeRoleRequest = Message<"host.installation.v1.RevokeRoleRequest"> & { /** - * The organisation the installation belongs to; a grant is an organisation - * administrator's act, and the host checks the actor against it before it - * reads the installation. Learned from the second implementation (the host). + * The organization the installation belongs to. * - * @generated from field: string organization_id = 3; + * @generated from field: string organization_id = 1; */ organizationId: string; + + /** + * The installation. + * + * @generated from field: string installation_id = 2; + */ + installationId: string; + + /** + * The role. + * + * @generated from field: string role = 3; + */ + role: string; + + /** + * The person; exactly one of person_id and team_id is set. + * + * @generated from field: string person_id = 4; + */ + personId: string; + + /** + * The team. + * + * @generated from field: string team_id = 5; + */ + teamId: string; }; /** - * Describes the message host.installation.v1.GrantToTeamRequest. - * Use `create(GrantToTeamRequestSchema)` to create a new message. + * Describes the message host.installation.v1.RevokeRoleRequest. + * Use `create(RevokeRoleRequestSchema)` to create a new message. */ -export const GrantToTeamRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_host_installation_v1_installation, 1); +export const RevokeRoleRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 2); + +/** + * ListRoleGrantsRequest names one installation. + * + * @generated from message host.installation.v1.ListRoleGrantsRequest + */ +export type ListRoleGrantsRequest = Message<"host.installation.v1.ListRoleGrantsRequest"> & { + /** + * The organization the installation belongs to. + * + * @generated from field: string organization_id = 1; + */ + organizationId: string; + + /** + * The installation. + * + * @generated from field: string installation_id = 2; + */ + installationId: string; +}; + +/** + * Describes the message host.installation.v1.ListRoleGrantsRequest. + * Use `create(ListRoleGrantsRequestSchema)` to create a new message. + */ +export const ListRoleGrantsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 3); + +/** + * ListRoleGrantsResponse is every grant on the installation. + * + * @generated from message host.installation.v1.ListRoleGrantsResponse + */ +export type ListRoleGrantsResponse = Message<"host.installation.v1.ListRoleGrantsResponse"> & { + /** + * The grants. + * + * @generated from field: repeated host.installation.v1.RoleGrant grants = 1; + */ + grants: RoleGrant[]; +}; + +/** + * Describes the message host.installation.v1.ListRoleGrantsResponse. + * Use `create(ListRoleGrantsResponseSchema)` to create a new message. + */ +export const ListRoleGrantsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 4); + +/** + * RoleGrant is one role held by one person or team. + * + * @generated from message host.installation.v1.RoleGrant + */ +export type RoleGrant = Message<"host.installation.v1.RoleGrant"> & { + /** + * The role. + * + * @generated from field: string role = 1; + */ + role: string; + + /** + * The person, when a person holds it. + * + * @generated from field: string person_id = 2; + */ + personId: string; + + /** + * The team, when a team holds it. + * + * @generated from field: string team_id = 3; + */ + teamId: string; +}; + +/** + * Describes the message host.installation.v1.RoleGrant. + * Use `create(RoleGrantSchema)` to create a new message. + */ +export const RoleGrantSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 5); /** * ListRequest asks for one organisation's installations. @@ -114,7 +264,7 @@ export type ListRequest = Message<"host.installation.v1.ListRequest"> & { * Use `create(ListRequestSchema)` to create a new message. */ export const ListRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_host_installation_v1_installation, 2); + messageDesc(file_host_installation_v1_installation, 6); /** * ListResponse lists installations. @@ -135,7 +285,7 @@ export type ListResponse = Message<"host.installation.v1.ListResponse"> & { * Use `create(ListResponseSchema)` to create a new message. */ export const ListResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_host_installation_v1_installation, 3); + messageDesc(file_host_installation_v1_installation, 7); /** * Installation is one organisation's installation of one target. @@ -179,35 +329,38 @@ export type Installation = Message<"host.installation.v1.Installation"> & { revision: bigint; /** - * The role the installation holds. + * The role the installation holds when it acts for itself (its agent's + * role); no person or team inherits it. * * @generated from field: string role = 6; */ role: string; /** - * The teams granted it. + * The namespace the installation's own authored content belongs to (an + * agent's source): the host names it, so a module never chooses which source + * an installation may run. A module installed by composition is + * ".". * - * @generated from field: repeated string team_ids = 7; + * @generated from field: string namespace = 9; */ - teamIds: string[]; + namespace: string; /** - * The principal the installation acts as for itself. + * The solution principal: the identity the installation's data is owned by + * and the installation acts as for itself, a stable UUID per organization + * and target, reused by a reinstall. Empty for a module installation. * - * @generated from field: string agent_principal_id = 8; + * @generated from field: string principal_id = 10; */ - agentPrincipalId: string; + principalId: string; /** - * The namespace the installation's own authored content belongs to (an - * agent's source): the host names it, so a module never chooses which source - * an installation may run. A module installed by composition is - * ".". + * The roles the solution declares, sorted. * - * @generated from field: string namespace = 9; + * @generated from field: repeated string roles = 11; */ - namespace: string; + roles: string[]; }; /** @@ -215,7 +368,7 @@ export type Installation = Message<"host.installation.v1.Installation"> & { * Use `create(InstallationSchema)` to create a new message. */ export const InstallationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_host_installation_v1_installation, 4); + messageDesc(file_host_installation_v1_installation, 8); /** * Status is the installation's lifecycle. @@ -256,7 +409,7 @@ export enum Installation_Status { * Describes the enum host.installation.v1.Installation.Status. */ export const Installation_StatusSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_host_installation_v1_installation, 4, 0); + enumDesc(file_host_installation_v1_installation, 8, 0); /** * InstallationNotice is what the host hands the solution's install step. @@ -277,7 +430,7 @@ export type InstallationNotice = Message<"host.installation.v1.InstallationNotic * Use `create(InstallationNoticeSchema)` to create a new message. */ export const InstallationNoticeSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_host_installation_v1_installation, 5); + messageDesc(file_host_installation_v1_installation, 9); /** * InstallationAck acknowledges the install step. @@ -292,7 +445,7 @@ export type InstallationAck = Message<"host.installation.v1.InstallationAck"> & * Use `create(InstallationAckSchema)` to create a new message. */ export const InstallationAckSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_host_installation_v1_installation, 6); + messageDesc(file_host_installation_v1_installation, 10); /** * InstallationService is implemented by the host and by the local authority. @@ -312,15 +465,40 @@ export const InstallationService: GenService<{ output: typeof InstallationSchema; }, /** - * GrantToTeam exposes an installation to a team under the installation's role. + * GrantRole grants a person or a team one of the solution's roles on the + * installation: the access. Idempotent; it leaves existing capabilities + * valid. An administrator of the installation's organization, or a holder + * of the installer role on that installation, grants. + * + * @generated from rpc host.installation.v1.InstallationService.GrantRole + */ + grantRole: { + methodKind: "unary"; + input: typeof GrantRoleRequestSchema; + output: typeof InstallationSchema; + }, + /** + * RevokeRole ends one grant; every capability its subject holds from it is + * refused at once. Idempotent; the same callers as GrantRole. * - * @generated from rpc host.installation.v1.InstallationService.GrantToTeam + * @generated from rpc host.installation.v1.InstallationService.RevokeRole */ - grantToTeam: { + revokeRole: { methodKind: "unary"; - input: typeof GrantToTeamRequestSchema; + input: typeof RevokeRoleRequestSchema; output: typeof InstallationSchema; }, + /** + * ListRoleGrants answers who holds which role on the installation; the same + * callers as GrantRole. + * + * @generated from rpc host.installation.v1.InstallationService.ListRoleGrants + */ + listRoleGrants: { + methodKind: "unary"; + input: typeof ListRoleGrantsRequestSchema; + output: typeof ListRoleGrantsResponseSchema; + }, /** * List answers what one organisation has installed: an administrator's read. A * member's view, which installed solutions they may use, is their navigation and diff --git a/modules/authority/module.codefly.yaml b/modules/authority/module.codefly.yaml index 6b7eb64c..75c9e1b0 100644 --- a/modules/authority/module.codefly.yaml +++ b/modules/authority/module.codefly.yaml @@ -8,9 +8,9 @@ interface: - service: local endpoint: grpc implements: - - codefly.dev/authority@0.2.0 - - codefly.dev/composition@0.1.0 - - codefly.dev/installation@0.1.0 + - codefly.dev/authority@0.3.0 + - codefly.dev/composition@0.2.0 + - codefly.dev/installation@0.2.0 - codefly.dev/session@0.2.0 # The REST face publishes the authority's keys as a JSON Web Key Set # (/.well-known/jwks.json), for a module that reads them by URL. diff --git a/modules/authority/services/edge/service.codefly.yaml b/modules/authority/services/edge/service.codefly.yaml index 617d07b4..1e700caf 100644 --- a/modules/authority/services/edge/service.codefly.yaml +++ b/modules/authority/services/edge/service.codefly.yaml @@ -15,9 +15,9 @@ agent: # saas accounts implements the same interfaces — and the local authority that # implements all three when it names none (code/host.go). service-dependencies: - - interface: codefly.dev/authority@0.2.0 + - interface: codefly.dev/authority@>=0.2.0 <0.4.0 - interface: codefly.dev/session@0.2.0 - - interface: codefly.dev/installation@0.1.0 + - interface: codefly.dev/installation@>=0.1.0 <0.3.0 workspace-configuration-dependencies: # The modules the edge routes to, their REST and Connect faces, and the # frontend every other path serves (code/main.go). diff --git a/modules/authority/services/hop/service.codefly.yaml b/modules/authority/services/hop/service.codefly.yaml index b8c73495..934acdf8 100644 --- a/modules/authority/services/hop/service.codefly.yaml +++ b/modules/authority/services/hop/service.codefly.yaml @@ -35,7 +35,7 @@ spec: # the composition names in the authority group's endpoint answers (saas # accounts, say), and the local authority when it names none (code/host.go). service-dependencies: - - interface: codefly.dev/authority@0.2.0 + - interface: codefly.dev/authority@>=0.2.0 <0.4.0 # The hop group: the run's proof, which the hop writes on every stamp so the # module behind it believes only stamps from this run's hop, and the module the # composition puts the hop in front of (MODULES). hop-routes is generated diff --git a/modules/authority/services/local/code/pkg/gen/host/authority/v1/authority.pb.go b/modules/authority/services/local/code/pkg/gen/host/authority/v1/authority.pb.go index 035f4404..114b93c5 100644 --- a/modules/authority/services/local/code/pkg/gen/host/authority/v1/authority.pb.go +++ b/modules/authority/services/local/code/pkg/gen/host/authority/v1/authority.pb.go @@ -1217,7 +1217,155 @@ func (x *ListResourcesResponse) GetGrants() []*ResourceGrant { return nil } -// ResourceGrant is one way of naming covered resources. Exactly one is set. +// AuthorizeTransferRequest names one existing resource and where it moves. +type AuthorizeTransferRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The capability of the person asking, addressed to the caller. + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + // The module serving resource_kind, proving which module it is. + Actor *WorkloadCredential `protobuf:"bytes,2,opt,name=actor,proto3" json:"actor,omitempty"` + // The resource kind, as the caller serves it. + ResourceKind string `protobuf:"bytes,3,opt,name=resource_kind,json=resourceKind,proto3" json:"resource_kind,omitempty"` + // The resource id, as the caller knows it. + ResourceId string `protobuf:"bytes,4,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` + // The resource's current owner, as the caller records it: the organization + // or a person of it. + FromOwnerId string `protobuf:"bytes,5,opt,name=from_owner_id,json=fromOwnerId,proto3" json:"from_owner_id,omitempty"` + // The solution principal the resource joins. + ToPrincipalId string `protobuf:"bytes,6,opt,name=to_principal_id,json=toPrincipalId,proto3" json:"to_principal_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizeTransferRequest) Reset() { + *x = AuthorizeTransferRequest{} + mi := &file_host_authority_v1_authority_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizeTransferRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizeTransferRequest) ProtoMessage() {} + +func (x *AuthorizeTransferRequest) ProtoReflect() protoreflect.Message { + mi := &file_host_authority_v1_authority_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizeTransferRequest.ProtoReflect.Descriptor instead. +func (*AuthorizeTransferRequest) Descriptor() ([]byte, []int) { + return file_host_authority_v1_authority_proto_rawDescGZIP(), []int{18} +} + +func (x *AuthorizeTransferRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *AuthorizeTransferRequest) GetActor() *WorkloadCredential { + if x != nil { + return x.Actor + } + return nil +} + +func (x *AuthorizeTransferRequest) GetResourceKind() string { + if x != nil { + return x.ResourceKind + } + return "" +} + +func (x *AuthorizeTransferRequest) GetResourceId() string { + if x != nil { + return x.ResourceId + } + return "" +} + +func (x *AuthorizeTransferRequest) GetFromOwnerId() string { + if x != nil { + return x.FromOwnerId + } + return "" +} + +func (x *AuthorizeTransferRequest) GetToPrincipalId() string { + if x != nil { + return x.ToPrincipalId + } + return "" +} + +// AuthorizeTransferResponse is the decision the store records. +type AuthorizeTransferResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The principal that now owns the resource. + PrincipalId string `protobuf:"bytes,1,opt,name=principal_id,json=principalId,proto3" json:"principal_id,omitempty"` + // The person who authorized the transfer. + AuthorizedBy string `protobuf:"bytes,2,opt,name=authorized_by,json=authorizedBy,proto3" json:"authorized_by,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizeTransferResponse) Reset() { + *x = AuthorizeTransferResponse{} + mi := &file_host_authority_v1_authority_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizeTransferResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizeTransferResponse) ProtoMessage() {} + +func (x *AuthorizeTransferResponse) ProtoReflect() protoreflect.Message { + mi := &file_host_authority_v1_authority_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizeTransferResponse.ProtoReflect.Descriptor instead. +func (*AuthorizeTransferResponse) Descriptor() ([]byte, []int) { + return file_host_authority_v1_authority_proto_rawDescGZIP(), []int{19} +} + +func (x *AuthorizeTransferResponse) GetPrincipalId() string { + if x != nil { + return x.PrincipalId + } + return "" +} + +func (x *AuthorizeTransferResponse) GetAuthorizedBy() string { + if x != nil { + return x.AuthorizedBy + } + return "" +} + +// ResourceGrant is one way of naming covered resources. Exactly one match is +// set; resource_owner_id, when set, qualifies it. type ResourceGrant struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Match: @@ -1226,14 +1374,17 @@ type ResourceGrant struct { // *ResourceGrant_Id // *ResourceGrant_Prefix // *ResourceGrant_Pattern - Match isResourceGrant_Match `protobuf_oneof:"match"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Match isResourceGrant_Match `protobuf_oneof:"match"` + // The owner the covered resources must have: a resource is covered when + // the match covers its id and it is owned by this principal. + ResourceOwnerId string `protobuf:"bytes,5,opt,name=resource_owner_id,json=resourceOwnerId,proto3" json:"resource_owner_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ResourceGrant) Reset() { *x = ResourceGrant{} - mi := &file_host_authority_v1_authority_proto_msgTypes[18] + mi := &file_host_authority_v1_authority_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1245,7 +1396,7 @@ func (x *ResourceGrant) String() string { func (*ResourceGrant) ProtoMessage() {} func (x *ResourceGrant) ProtoReflect() protoreflect.Message { - mi := &file_host_authority_v1_authority_proto_msgTypes[18] + mi := &file_host_authority_v1_authority_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1258,7 +1409,7 @@ func (x *ResourceGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceGrant.ProtoReflect.Descriptor instead. func (*ResourceGrant) Descriptor() ([]byte, []int) { - return file_host_authority_v1_authority_proto_rawDescGZIP(), []int{18} + return file_host_authority_v1_authority_proto_rawDescGZIP(), []int{20} } func (x *ResourceGrant) GetMatch() isResourceGrant_Match { @@ -1304,6 +1455,13 @@ func (x *ResourceGrant) GetPattern() string { return "" } +func (x *ResourceGrant) GetResourceOwnerId() string { + if x != nil { + return x.ResourceOwnerId + } + return "" +} + type isResourceGrant_Match interface { isResourceGrant_Match() } @@ -1420,18 +1578,31 @@ const file_host_authority_v1_authority_proto_rawDesc = "" + "\rresource_kind\x18\x03 \x01(\tR\fresourceKind\x12\x16\n" + "\x06action\x18\x04 \x01(\tR\x06action\"Q\n" + "\x15ListResourcesResponse\x128\n" + - "\x06grants\x18\x01 \x03(\v2 .host.authority.v1.ResourceGrantR\x06grants\"t\n" + + "\x06grants\x18\x01 \x03(\v2 .host.authority.v1.ResourceGrantR\x06grants\"\xff\x01\n" + + "\x18AuthorizeTransferRequest\x12\x14\n" + + "\x05token\x18\x01 \x01(\tR\x05token\x12;\n" + + "\x05actor\x18\x02 \x01(\v2%.host.authority.v1.WorkloadCredentialR\x05actor\x12#\n" + + "\rresource_kind\x18\x03 \x01(\tR\fresourceKind\x12\x1f\n" + + "\vresource_id\x18\x04 \x01(\tR\n" + + "resourceId\x12\"\n" + + "\rfrom_owner_id\x18\x05 \x01(\tR\vfromOwnerId\x12&\n" + + "\x0fto_principal_id\x18\x06 \x01(\tR\rtoPrincipalId\"c\n" + + "\x19AuthorizeTransferResponse\x12!\n" + + "\fprincipal_id\x18\x01 \x01(\tR\vprincipalId\x12#\n" + + "\rauthorized_by\x18\x02 \x01(\tR\fauthorizedBy\"\xa0\x01\n" + "\rResourceGrant\x12\x12\n" + "\x03all\x18\x01 \x01(\bH\x00R\x03all\x12\x10\n" + "\x02id\x18\x02 \x01(\tH\x00R\x02id\x12\x18\n" + "\x06prefix\x18\x03 \x01(\tH\x00R\x06prefix\x12\x1a\n" + - "\apattern\x18\x04 \x01(\tH\x00R\apatternB\a\n" + - "\x05match2\xac\x05\n" + + "\apattern\x18\x04 \x01(\tH\x00R\apattern\x12*\n" + + "\x11resource_owner_id\x18\x05 \x01(\tR\x0fresourceOwnerIdB\a\n" + + "\x05match2\x9c\x06\n" + "\x10AuthorityService\x12j\n" + "\x05Issue\x12\x1f.host.authority.v1.IssueRequest\x1a .host.authority.v1.IssueResponse\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/v1/authority:issue\x12j\n" + "\x05Check\x12\x1f.host.authority.v1.CheckRequest\x1a .host.authority.v1.CheckResponse\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/v1/authority:check\x12G\n" + "\x04Keys\x12\x1e.host.authority.v1.KeysRequest\x1a\x1f.host.authority.v1.KeysResponse\x12b\n" + - "\rListResources\x12'.host.authority.v1.ListResourcesRequest\x1a(.host.authority.v1.ListResourcesResponse\x12M\n" + + "\rListResources\x12'.host.authority.v1.ListResourcesRequest\x1a(.host.authority.v1.ListResourcesResponse\x12n\n" + + "\x11AuthorizeTransfer\x12+.host.authority.v1.AuthorizeTransferRequest\x1a,.host.authority.v1.AuthorizeTransferResponse\x12M\n" + "\bDelegate\x12\".host.authority.v1.DelegateRequest\x1a\x1d.host.authority.v1.Delegation\x12k\n" + "\x10RevokeDelegation\x12*.host.authority.v1.RevokeDelegationRequest\x1a+.host.authority.v1.RevokeDelegationResponse\x12W\n" + "\rGetDelegation\x12'.host.authority.v1.GetDelegationRequest\x1a\x1d.host.authority.v1.DelegationB/Z-gateway/pkg/gen/host/authority/v1;authorityv1b\x06proto3" @@ -1448,64 +1619,69 @@ func file_host_authority_v1_authority_proto_rawDescGZIP() []byte { return file_host_authority_v1_authority_proto_rawDescData } -var file_host_authority_v1_authority_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_host_authority_v1_authority_proto_msgTypes = make([]protoimpl.MessageInfo, 21) var file_host_authority_v1_authority_proto_goTypes = []any{ - (*IssueRequest)(nil), // 0: host.authority.v1.IssueRequest - (*DelegationRef)(nil), // 1: host.authority.v1.DelegationRef - (*DelegateRequest)(nil), // 2: host.authority.v1.DelegateRequest - (*Delegation)(nil), // 3: host.authority.v1.Delegation - (*GetDelegationRequest)(nil), // 4: host.authority.v1.GetDelegationRequest - (*RevokeDelegationRequest)(nil), // 5: host.authority.v1.RevokeDelegationRequest - (*RevokeDelegationResponse)(nil), // 6: host.authority.v1.RevokeDelegationResponse - (*PersonSession)(nil), // 7: host.authority.v1.PersonSession - (*WorkloadCredential)(nil), // 8: host.authority.v1.WorkloadCredential - (*InstallationRef)(nil), // 9: host.authority.v1.InstallationRef - (*IssueResponse)(nil), // 10: host.authority.v1.IssueResponse - (*CheckRequest)(nil), // 11: host.authority.v1.CheckRequest - (*CheckResponse)(nil), // 12: host.authority.v1.CheckResponse - (*KeysRequest)(nil), // 13: host.authority.v1.KeysRequest - (*KeysResponse)(nil), // 14: host.authority.v1.KeysResponse - (*PublicKey)(nil), // 15: host.authority.v1.PublicKey - (*ListResourcesRequest)(nil), // 16: host.authority.v1.ListResourcesRequest - (*ListResourcesResponse)(nil), // 17: host.authority.v1.ListResourcesResponse - (*ResourceGrant)(nil), // 18: host.authority.v1.ResourceGrant - (*WorkScopeV1)(nil), // 19: host.authority.v1.WorkScopeV1 - (*WorkContextV1)(nil), // 20: host.authority.v1.WorkContextV1 + (*IssueRequest)(nil), // 0: host.authority.v1.IssueRequest + (*DelegationRef)(nil), // 1: host.authority.v1.DelegationRef + (*DelegateRequest)(nil), // 2: host.authority.v1.DelegateRequest + (*Delegation)(nil), // 3: host.authority.v1.Delegation + (*GetDelegationRequest)(nil), // 4: host.authority.v1.GetDelegationRequest + (*RevokeDelegationRequest)(nil), // 5: host.authority.v1.RevokeDelegationRequest + (*RevokeDelegationResponse)(nil), // 6: host.authority.v1.RevokeDelegationResponse + (*PersonSession)(nil), // 7: host.authority.v1.PersonSession + (*WorkloadCredential)(nil), // 8: host.authority.v1.WorkloadCredential + (*InstallationRef)(nil), // 9: host.authority.v1.InstallationRef + (*IssueResponse)(nil), // 10: host.authority.v1.IssueResponse + (*CheckRequest)(nil), // 11: host.authority.v1.CheckRequest + (*CheckResponse)(nil), // 12: host.authority.v1.CheckResponse + (*KeysRequest)(nil), // 13: host.authority.v1.KeysRequest + (*KeysResponse)(nil), // 14: host.authority.v1.KeysResponse + (*PublicKey)(nil), // 15: host.authority.v1.PublicKey + (*ListResourcesRequest)(nil), // 16: host.authority.v1.ListResourcesRequest + (*ListResourcesResponse)(nil), // 17: host.authority.v1.ListResourcesResponse + (*AuthorizeTransferRequest)(nil), // 18: host.authority.v1.AuthorizeTransferRequest + (*AuthorizeTransferResponse)(nil), // 19: host.authority.v1.AuthorizeTransferResponse + (*ResourceGrant)(nil), // 20: host.authority.v1.ResourceGrant + (*WorkScopeV1)(nil), // 21: host.authority.v1.WorkScopeV1 + (*WorkContextV1)(nil), // 22: host.authority.v1.WorkContextV1 } var file_host_authority_v1_authority_proto_depIdxs = []int32{ 7, // 0: host.authority.v1.IssueRequest.person:type_name -> host.authority.v1.PersonSession 8, // 1: host.authority.v1.IssueRequest.workload:type_name -> host.authority.v1.WorkloadCredential 9, // 2: host.authority.v1.IssueRequest.installation:type_name -> host.authority.v1.InstallationRef 1, // 3: host.authority.v1.IssueRequest.delegation:type_name -> host.authority.v1.DelegationRef - 19, // 4: host.authority.v1.IssueRequest.scopes:type_name -> host.authority.v1.WorkScopeV1 + 21, // 4: host.authority.v1.IssueRequest.scopes:type_name -> host.authority.v1.WorkScopeV1 8, // 5: host.authority.v1.IssueRequest.actor:type_name -> host.authority.v1.WorkloadCredential - 19, // 6: host.authority.v1.DelegateRequest.scopes:type_name -> host.authority.v1.WorkScopeV1 - 19, // 7: host.authority.v1.Delegation.scopes:type_name -> host.authority.v1.WorkScopeV1 + 21, // 6: host.authority.v1.DelegateRequest.scopes:type_name -> host.authority.v1.WorkScopeV1 + 21, // 7: host.authority.v1.Delegation.scopes:type_name -> host.authority.v1.WorkScopeV1 8, // 8: host.authority.v1.GetDelegationRequest.actor:type_name -> host.authority.v1.WorkloadCredential 3, // 9: host.authority.v1.RevokeDelegationResponse.delegation:type_name -> host.authority.v1.Delegation - 20, // 10: host.authority.v1.IssueResponse.context:type_name -> host.authority.v1.WorkContextV1 - 20, // 11: host.authority.v1.CheckResponse.context:type_name -> host.authority.v1.WorkContextV1 + 22, // 10: host.authority.v1.IssueResponse.context:type_name -> host.authority.v1.WorkContextV1 + 22, // 11: host.authority.v1.CheckResponse.context:type_name -> host.authority.v1.WorkContextV1 15, // 12: host.authority.v1.KeysResponse.keys:type_name -> host.authority.v1.PublicKey - 18, // 13: host.authority.v1.ListResourcesResponse.grants:type_name -> host.authority.v1.ResourceGrant - 0, // 14: host.authority.v1.AuthorityService.Issue:input_type -> host.authority.v1.IssueRequest - 11, // 15: host.authority.v1.AuthorityService.Check:input_type -> host.authority.v1.CheckRequest - 13, // 16: host.authority.v1.AuthorityService.Keys:input_type -> host.authority.v1.KeysRequest - 16, // 17: host.authority.v1.AuthorityService.ListResources:input_type -> host.authority.v1.ListResourcesRequest - 2, // 18: host.authority.v1.AuthorityService.Delegate:input_type -> host.authority.v1.DelegateRequest - 5, // 19: host.authority.v1.AuthorityService.RevokeDelegation:input_type -> host.authority.v1.RevokeDelegationRequest - 4, // 20: host.authority.v1.AuthorityService.GetDelegation:input_type -> host.authority.v1.GetDelegationRequest - 10, // 21: host.authority.v1.AuthorityService.Issue:output_type -> host.authority.v1.IssueResponse - 12, // 22: host.authority.v1.AuthorityService.Check:output_type -> host.authority.v1.CheckResponse - 14, // 23: host.authority.v1.AuthorityService.Keys:output_type -> host.authority.v1.KeysResponse - 17, // 24: host.authority.v1.AuthorityService.ListResources:output_type -> host.authority.v1.ListResourcesResponse - 3, // 25: host.authority.v1.AuthorityService.Delegate:output_type -> host.authority.v1.Delegation - 6, // 26: host.authority.v1.AuthorityService.RevokeDelegation:output_type -> host.authority.v1.RevokeDelegationResponse - 3, // 27: host.authority.v1.AuthorityService.GetDelegation:output_type -> host.authority.v1.Delegation - 21, // [21:28] is the sub-list for method output_type - 14, // [14:21] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 20, // 13: host.authority.v1.ListResourcesResponse.grants:type_name -> host.authority.v1.ResourceGrant + 8, // 14: host.authority.v1.AuthorizeTransferRequest.actor:type_name -> host.authority.v1.WorkloadCredential + 0, // 15: host.authority.v1.AuthorityService.Issue:input_type -> host.authority.v1.IssueRequest + 11, // 16: host.authority.v1.AuthorityService.Check:input_type -> host.authority.v1.CheckRequest + 13, // 17: host.authority.v1.AuthorityService.Keys:input_type -> host.authority.v1.KeysRequest + 16, // 18: host.authority.v1.AuthorityService.ListResources:input_type -> host.authority.v1.ListResourcesRequest + 18, // 19: host.authority.v1.AuthorityService.AuthorizeTransfer:input_type -> host.authority.v1.AuthorizeTransferRequest + 2, // 20: host.authority.v1.AuthorityService.Delegate:input_type -> host.authority.v1.DelegateRequest + 5, // 21: host.authority.v1.AuthorityService.RevokeDelegation:input_type -> host.authority.v1.RevokeDelegationRequest + 4, // 22: host.authority.v1.AuthorityService.GetDelegation:input_type -> host.authority.v1.GetDelegationRequest + 10, // 23: host.authority.v1.AuthorityService.Issue:output_type -> host.authority.v1.IssueResponse + 12, // 24: host.authority.v1.AuthorityService.Check:output_type -> host.authority.v1.CheckResponse + 14, // 25: host.authority.v1.AuthorityService.Keys:output_type -> host.authority.v1.KeysResponse + 17, // 26: host.authority.v1.AuthorityService.ListResources:output_type -> host.authority.v1.ListResourcesResponse + 19, // 27: host.authority.v1.AuthorityService.AuthorizeTransfer:output_type -> host.authority.v1.AuthorizeTransferResponse + 3, // 28: host.authority.v1.AuthorityService.Delegate:output_type -> host.authority.v1.Delegation + 6, // 29: host.authority.v1.AuthorityService.RevokeDelegation:output_type -> host.authority.v1.RevokeDelegationResponse + 3, // 30: host.authority.v1.AuthorityService.GetDelegation:output_type -> host.authority.v1.Delegation + 23, // [23:31] is the sub-list for method output_type + 15, // [15:23] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name } func init() { file_host_authority_v1_authority_proto_init() } @@ -1521,7 +1697,7 @@ func file_host_authority_v1_authority_proto_init() { (*IssueRequest_ParentToken)(nil), (*IssueRequest_Delegation)(nil), } - file_host_authority_v1_authority_proto_msgTypes[18].OneofWrappers = []any{ + file_host_authority_v1_authority_proto_msgTypes[20].OneofWrappers = []any{ (*ResourceGrant_All)(nil), (*ResourceGrant_Id)(nil), (*ResourceGrant_Prefix)(nil), @@ -1533,7 +1709,7 @@ func file_host_authority_v1_authority_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_host_authority_v1_authority_proto_rawDesc), len(file_host_authority_v1_authority_proto_rawDesc)), NumEnums: 0, - NumMessages: 19, + NumMessages: 21, NumExtensions: 0, NumServices: 1, }, diff --git a/modules/authority/services/local/code/pkg/gen/host/authority/v1/authority_grpc.pb.go b/modules/authority/services/local/code/pkg/gen/host/authority/v1/authority_grpc.pb.go index 51d772ee..e548980e 100644 --- a/modules/authority/services/local/code/pkg/gen/host/authority/v1/authority_grpc.pb.go +++ b/modules/authority/services/local/code/pkg/gen/host/authority/v1/authority_grpc.pb.go @@ -26,13 +26,14 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - AuthorityService_Issue_FullMethodName = "/host.authority.v1.AuthorityService/Issue" - AuthorityService_Check_FullMethodName = "/host.authority.v1.AuthorityService/Check" - AuthorityService_Keys_FullMethodName = "/host.authority.v1.AuthorityService/Keys" - AuthorityService_ListResources_FullMethodName = "/host.authority.v1.AuthorityService/ListResources" - AuthorityService_Delegate_FullMethodName = "/host.authority.v1.AuthorityService/Delegate" - AuthorityService_RevokeDelegation_FullMethodName = "/host.authority.v1.AuthorityService/RevokeDelegation" - AuthorityService_GetDelegation_FullMethodName = "/host.authority.v1.AuthorityService/GetDelegation" + AuthorityService_Issue_FullMethodName = "/host.authority.v1.AuthorityService/Issue" + AuthorityService_Check_FullMethodName = "/host.authority.v1.AuthorityService/Check" + AuthorityService_Keys_FullMethodName = "/host.authority.v1.AuthorityService/Keys" + AuthorityService_ListResources_FullMethodName = "/host.authority.v1.AuthorityService/ListResources" + AuthorityService_AuthorizeTransfer_FullMethodName = "/host.authority.v1.AuthorityService/AuthorizeTransfer" + AuthorityService_Delegate_FullMethodName = "/host.authority.v1.AuthorityService/Delegate" + AuthorityService_RevokeDelegation_FullMethodName = "/host.authority.v1.AuthorityService/RevokeDelegation" + AuthorityService_GetDelegation_FullMethodName = "/host.authority.v1.AuthorityService/GetDelegation" ) // AuthorityServiceClient is the client API for AuthorityService service. @@ -57,6 +58,19 @@ type AuthorityServiceClient interface { // rather than carried in the capability. A module maps the answer onto its // own resources; the authority knows them only by id. ListResources(ctx context.Context, in *ListResourcesRequest, opts ...grpc.CallOption) (*ListResourcesResponse, error) + // AuthorizeTransfer decides whether an existing resource may join an + // installed solution's principal. The caller is the kind's one serving + // module, which attests the resource exists and who owns it now + // (from_owner_id), presenting the capability of the person asking: issued + // through the target principal's installation and addressed to the caller. + // It is allowed when the current owner is the organization or a person of + // it (never another solution principal, another organization or an unknown + // owner), the person holds admin on that resource from their own + // organization grants, and the person's current roles on the principal hold + // write on the kind, all read live. An installation acting for itself never + // transfers. The store then records the principal as owner; the original + // creator stays the author. + AuthorizeTransfer(ctx context.Context, in *AuthorizeTransferRequest, opts ...grpc.CallOption) (*AuthorizeTransferResponse, error) // Delegate records a signed-in person's grant to a module (the caller's // bearer is the person); Issue with a delegation acts on it. Delegate(ctx context.Context, in *DelegateRequest, opts ...grpc.CallOption) (*Delegation, error) @@ -117,6 +131,16 @@ func (c *authorityServiceClient) ListResources(ctx context.Context, in *ListReso return out, nil } +func (c *authorityServiceClient) AuthorizeTransfer(ctx context.Context, in *AuthorizeTransferRequest, opts ...grpc.CallOption) (*AuthorizeTransferResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthorizeTransferResponse) + err := c.cc.Invoke(ctx, AuthorityService_AuthorizeTransfer_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *authorityServiceClient) Delegate(ctx context.Context, in *DelegateRequest, opts ...grpc.CallOption) (*Delegation, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Delegation) @@ -169,6 +193,19 @@ type AuthorityServiceServer interface { // rather than carried in the capability. A module maps the answer onto its // own resources; the authority knows them only by id. ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) + // AuthorizeTransfer decides whether an existing resource may join an + // installed solution's principal. The caller is the kind's one serving + // module, which attests the resource exists and who owns it now + // (from_owner_id), presenting the capability of the person asking: issued + // through the target principal's installation and addressed to the caller. + // It is allowed when the current owner is the organization or a person of + // it (never another solution principal, another organization or an unknown + // owner), the person holds admin on that resource from their own + // organization grants, and the person's current roles on the principal hold + // write on the kind, all read live. An installation acting for itself never + // transfers. The store then records the principal as owner; the original + // creator stays the author. + AuthorizeTransfer(context.Context, *AuthorizeTransferRequest) (*AuthorizeTransferResponse, error) // Delegate records a signed-in person's grant to a module (the caller's // bearer is the person); Issue with a delegation acts on it. Delegate(context.Context, *DelegateRequest) (*Delegation, error) @@ -201,6 +238,9 @@ func (UnimplementedAuthorityServiceServer) Keys(context.Context, *KeysRequest) ( func (UnimplementedAuthorityServiceServer) ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) { return nil, status.Error(codes.Unimplemented, "method ListResources not implemented") } +func (UnimplementedAuthorityServiceServer) AuthorizeTransfer(context.Context, *AuthorizeTransferRequest) (*AuthorizeTransferResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AuthorizeTransfer not implemented") +} func (UnimplementedAuthorityServiceServer) Delegate(context.Context, *DelegateRequest) (*Delegation, error) { return nil, status.Error(codes.Unimplemented, "method Delegate not implemented") } @@ -303,6 +343,24 @@ func _AuthorityService_ListResources_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _AuthorityService_AuthorizeTransfer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthorizeTransferRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorityServiceServer).AuthorizeTransfer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorityService_AuthorizeTransfer_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorityServiceServer).AuthorizeTransfer(ctx, req.(*AuthorizeTransferRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _AuthorityService_Delegate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DelegateRequest) if err := dec(in); err != nil { @@ -380,6 +438,10 @@ var AuthorityService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListResources", Handler: _AuthorityService_ListResources_Handler, }, + { + MethodName: "AuthorizeTransfer", + Handler: _AuthorityService_AuthorizeTransfer_Handler, + }, { MethodName: "Delegate", Handler: _AuthorityService_Delegate_Handler, diff --git a/modules/authority/services/local/code/pkg/gen/host/authority/v1/authorityv1connect/authority.connect.go b/modules/authority/services/local/code/pkg/gen/host/authority/v1/authorityv1connect/authority.connect.go index 497d1a03..f04ce2fd 100644 --- a/modules/authority/services/local/code/pkg/gen/host/authority/v1/authorityv1connect/authority.connect.go +++ b/modules/authority/services/local/code/pkg/gen/host/authority/v1/authorityv1connect/authority.connect.go @@ -48,6 +48,9 @@ const ( // AuthorityServiceListResourcesProcedure is the fully-qualified name of the AuthorityService's // ListResources RPC. AuthorityServiceListResourcesProcedure = "/host.authority.v1.AuthorityService/ListResources" + // AuthorityServiceAuthorizeTransferProcedure is the fully-qualified name of the AuthorityService's + // AuthorizeTransfer RPC. + AuthorityServiceAuthorizeTransferProcedure = "/host.authority.v1.AuthorityService/AuthorizeTransfer" // AuthorityServiceDelegateProcedure is the fully-qualified name of the AuthorityService's Delegate // RPC. AuthorityServiceDelegateProcedure = "/host.authority.v1.AuthorityService/Delegate" @@ -77,6 +80,19 @@ type AuthorityServiceClient interface { // rather than carried in the capability. A module maps the answer onto its // own resources; the authority knows them only by id. ListResources(context.Context, *connect.Request[v1.ListResourcesRequest]) (*connect.Response[v1.ListResourcesResponse], error) + // AuthorizeTransfer decides whether an existing resource may join an + // installed solution's principal. The caller is the kind's one serving + // module, which attests the resource exists and who owns it now + // (from_owner_id), presenting the capability of the person asking: issued + // through the target principal's installation and addressed to the caller. + // It is allowed when the current owner is the organization or a person of + // it (never another solution principal, another organization or an unknown + // owner), the person holds admin on that resource from their own + // organization grants, and the person's current roles on the principal hold + // write on the kind, all read live. An installation acting for itself never + // transfers. The store then records the principal as owner; the original + // creator stays the author. + AuthorizeTransfer(context.Context, *connect.Request[v1.AuthorizeTransferRequest]) (*connect.Response[v1.AuthorizeTransferResponse], error) // Delegate records a signed-in person's grant to a module (the caller's // bearer is the person); Issue with a delegation acts on it. Delegate(context.Context, *connect.Request[v1.DelegateRequest]) (*connect.Response[v1.Delegation], error) @@ -124,6 +140,12 @@ func NewAuthorityServiceClient(httpClient connect.HTTPClient, baseURL string, op connect.WithSchema(authorityServiceMethods.ByName("ListResources")), connect.WithClientOptions(opts...), ), + authorizeTransfer: connect.NewClient[v1.AuthorizeTransferRequest, v1.AuthorizeTransferResponse]( + httpClient, + baseURL+AuthorityServiceAuthorizeTransferProcedure, + connect.WithSchema(authorityServiceMethods.ByName("AuthorizeTransfer")), + connect.WithClientOptions(opts...), + ), delegate: connect.NewClient[v1.DelegateRequest, v1.Delegation]( httpClient, baseURL+AuthorityServiceDelegateProcedure, @@ -147,13 +169,14 @@ func NewAuthorityServiceClient(httpClient connect.HTTPClient, baseURL string, op // authorityServiceClient implements AuthorityServiceClient. type authorityServiceClient struct { - issue *connect.Client[v1.IssueRequest, v1.IssueResponse] - check *connect.Client[v1.CheckRequest, v1.CheckResponse] - keys *connect.Client[v1.KeysRequest, v1.KeysResponse] - listResources *connect.Client[v1.ListResourcesRequest, v1.ListResourcesResponse] - delegate *connect.Client[v1.DelegateRequest, v1.Delegation] - revokeDelegation *connect.Client[v1.RevokeDelegationRequest, v1.RevokeDelegationResponse] - getDelegation *connect.Client[v1.GetDelegationRequest, v1.Delegation] + issue *connect.Client[v1.IssueRequest, v1.IssueResponse] + check *connect.Client[v1.CheckRequest, v1.CheckResponse] + keys *connect.Client[v1.KeysRequest, v1.KeysResponse] + listResources *connect.Client[v1.ListResourcesRequest, v1.ListResourcesResponse] + authorizeTransfer *connect.Client[v1.AuthorizeTransferRequest, v1.AuthorizeTransferResponse] + delegate *connect.Client[v1.DelegateRequest, v1.Delegation] + revokeDelegation *connect.Client[v1.RevokeDelegationRequest, v1.RevokeDelegationResponse] + getDelegation *connect.Client[v1.GetDelegationRequest, v1.Delegation] } // Issue calls host.authority.v1.AuthorityService.Issue. @@ -176,6 +199,11 @@ func (c *authorityServiceClient) ListResources(ctx context.Context, req *connect return c.listResources.CallUnary(ctx, req) } +// AuthorizeTransfer calls host.authority.v1.AuthorityService.AuthorizeTransfer. +func (c *authorityServiceClient) AuthorizeTransfer(ctx context.Context, req *connect.Request[v1.AuthorizeTransferRequest]) (*connect.Response[v1.AuthorizeTransferResponse], error) { + return c.authorizeTransfer.CallUnary(ctx, req) +} + // Delegate calls host.authority.v1.AuthorityService.Delegate. func (c *authorityServiceClient) Delegate(ctx context.Context, req *connect.Request[v1.DelegateRequest]) (*connect.Response[v1.Delegation], error) { return c.delegate.CallUnary(ctx, req) @@ -209,6 +237,19 @@ type AuthorityServiceHandler interface { // rather than carried in the capability. A module maps the answer onto its // own resources; the authority knows them only by id. ListResources(context.Context, *connect.Request[v1.ListResourcesRequest]) (*connect.Response[v1.ListResourcesResponse], error) + // AuthorizeTransfer decides whether an existing resource may join an + // installed solution's principal. The caller is the kind's one serving + // module, which attests the resource exists and who owns it now + // (from_owner_id), presenting the capability of the person asking: issued + // through the target principal's installation and addressed to the caller. + // It is allowed when the current owner is the organization or a person of + // it (never another solution principal, another organization or an unknown + // owner), the person holds admin on that resource from their own + // organization grants, and the person's current roles on the principal hold + // write on the kind, all read live. An installation acting for itself never + // transfers. The store then records the principal as owner; the original + // creator stays the author. + AuthorizeTransfer(context.Context, *connect.Request[v1.AuthorizeTransferRequest]) (*connect.Response[v1.AuthorizeTransferResponse], error) // Delegate records a signed-in person's grant to a module (the caller's // bearer is the person); Issue with a delegation acts on it. Delegate(context.Context, *connect.Request[v1.DelegateRequest]) (*connect.Response[v1.Delegation], error) @@ -252,6 +293,12 @@ func NewAuthorityServiceHandler(svc AuthorityServiceHandler, opts ...connect.Han connect.WithSchema(authorityServiceMethods.ByName("ListResources")), connect.WithHandlerOptions(opts...), ) + authorityServiceAuthorizeTransferHandler := connect.NewUnaryHandler( + AuthorityServiceAuthorizeTransferProcedure, + svc.AuthorizeTransfer, + connect.WithSchema(authorityServiceMethods.ByName("AuthorizeTransfer")), + connect.WithHandlerOptions(opts...), + ) authorityServiceDelegateHandler := connect.NewUnaryHandler( AuthorityServiceDelegateProcedure, svc.Delegate, @@ -280,6 +327,8 @@ func NewAuthorityServiceHandler(svc AuthorityServiceHandler, opts ...connect.Han authorityServiceKeysHandler.ServeHTTP(w, r) case AuthorityServiceListResourcesProcedure: authorityServiceListResourcesHandler.ServeHTTP(w, r) + case AuthorityServiceAuthorizeTransferProcedure: + authorityServiceAuthorizeTransferHandler.ServeHTTP(w, r) case AuthorityServiceDelegateProcedure: authorityServiceDelegateHandler.ServeHTTP(w, r) case AuthorityServiceRevokeDelegationProcedure: @@ -311,6 +360,10 @@ func (UnimplementedAuthorityServiceHandler) ListResources(context.Context, *conn return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.authority.v1.AuthorityService.ListResources is not implemented")) } +func (UnimplementedAuthorityServiceHandler) AuthorizeTransfer(context.Context, *connect.Request[v1.AuthorizeTransferRequest]) (*connect.Response[v1.AuthorizeTransferResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.authority.v1.AuthorityService.AuthorizeTransfer is not implemented")) +} + func (UnimplementedAuthorityServiceHandler) Delegate(context.Context, *connect.Request[v1.DelegateRequest]) (*connect.Response[v1.Delegation], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.authority.v1.AuthorityService.Delegate is not implemented")) } diff --git a/modules/authority/services/local/code/pkg/gen/host/composition/v1/composition.pb.go b/modules/authority/services/local/code/pkg/gen/host/composition/v1/composition.pb.go index 31bc6961..f1416503 100644 --- a/modules/authority/services/local/code/pkg/gen/host/composition/v1/composition.pb.go +++ b/modules/authority/services/local/code/pkg/gen/host/composition/v1/composition.pb.go @@ -136,7 +136,12 @@ type AuthorityManifestV1 struct { // one of them installer. Headless-only scopes cannot supply a role grant. // Core's manifest carries no roles, so they travel here until it does. A // module declares none; a module's delivery listing any is refused. - Roles []*RoleV1 `protobuf:"bytes,6,rep,name=roles,proto3" json:"roles,omitempty"` + Roles []*RoleV1 `protobuf:"bytes,6,rep,name=roles,proto3" json:"roles,omitempty"` + // The module checks a resource's owner against an owner-selected scope + // (Claims.AllowsOwned): only an audience declaring it receives one, and a + // delivery withdrawing it refuses the owner-selected capabilities issued + // before. Every solution receiving its roles' scopes declares it. + OwnerAware bool `protobuf:"varint,7,opt,name=owner_aware,json=ownerAware,proto3" json:"owner_aware,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -213,6 +218,13 @@ func (x *AuthorityManifestV1) GetRoles() []*RoleV1 { return nil } +func (x *AuthorityManifestV1) GetOwnerAware() bool { + if x != nil { + return x.OwnerAware + } + return false +} + // MethodPolicyV1 is what a call to one method must hold, as the module's // contract declares it with the authority.hop.v1.policy option. type MethodPolicyV1 struct { @@ -417,7 +429,7 @@ type ModuleManifestV1 struct { Page *PageV1 `protobuf:"bytes,7,opt,name=page,proto3" json:"page,omitempty"` // The roles a solution declares a person may hold on an installation of it, // each within what the solution serves or explicitly consumes through - // invoke/lookup bindings; one is named installer, held by whoever installs it. + // invoke/lookup bindings; one is named installer, held only by whom it is granted to. // Headless-only scopes cannot supply a role grant. A module declares none. Roles []*RoleV1 `protobuf:"bytes,8,rep,name=roles,proto3" json:"roles,omitempty"` unknownFields protoimpl.UnknownFields @@ -769,7 +781,7 @@ const file_host_composition_v1_composition_proto_rawDesc = "" + "\n" + "credential\x18\x03 \x01(\tR\n" + "credential\x12C\n" + - "\tauthority\x18\x04 \x01(\v2%.host.composition.v1.SignedManifestV1R\tauthority\"\xb8\x02\n" + + "\tauthority\x18\x04 \x01(\v2%.host.composition.v1.SignedManifestV1R\tauthority\"\xd9\x02\n" + "\x13AuthorityManifestV1\x12\x16\n" + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1e\n" + "\n" + @@ -778,7 +790,9 @@ const file_host_composition_v1_composition_proto_rawDesc = "" + "\x14core_manifest_digest\x18\x03 \x01(\tR\x12coreManifestDigest\x12=\n" + "\amethods\x18\x04 \x03(\v2#.host.composition.v1.MethodPolicyV1R\amethods\x12E\n" + "\x0escope_ceilings\x18\x05 \x03(\v2\x1e.host.authority.v1.WorkScopeV1R\rscopeCeilings\x121\n" + - "\x05roles\x18\x06 \x03(\v2\x1b.host.composition.v1.RoleV1R\x05roles\"\x92\x01\n" + + "\x05roles\x18\x06 \x03(\v2\x1b.host.composition.v1.RoleV1R\x05roles\x12\x1f\n" + + "\vowner_aware\x18\a \x01(\bR\n" + + "ownerAware\"\x92\x01\n" + "\x0eMethodPolicyV1\x12\x16\n" + "\x06method\x18\x01 \x01(\tR\x06method\x12:\n" + "\brequires\x18\x02 \x03(\v2\x1e.host.authority.v1.WorkScopeV1R\brequires\x12,\n" + diff --git a/modules/authority/services/local/code/pkg/gen/host/installation/v1/installation.pb.go b/modules/authority/services/local/code/pkg/gen/host/installation/v1/installation.pb.go index 79b99280..63f94348 100644 --- a/modules/authority/services/local/code/pkg/gen/host/installation/v1/installation.pb.go +++ b/modules/authority/services/local/code/pkg/gen/host/installation/v1/installation.pb.go @@ -5,8 +5,10 @@ // source: host/installation/v1/installation.proto // The installation interface: what is installed at an organisation and who may -// use it. Three operations on the host, and one the solution implements, the -// install step the host calls once per installation. +// use it. An installed solution is a principal of its organisation; its roles +// are levels on the data that principal owns, and a grant of a role is the +// access. Operations on the host, and one the solution implements, the install +// step the host calls once per installation. package installationv1 @@ -80,7 +82,7 @@ func (x Installation_Status) Number() protoreflect.EnumNumber { // Deprecated: Use Installation_Status.Descriptor instead. func (Installation_Status) EnumDescriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{4, 0} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{8, 0} } // InstallRequest asks to install one target at one organisation. @@ -156,35 +158,40 @@ func (x *InstallRequest) GetOwnerPersonId() string { return "" } -// GrantToTeamRequest exposes an installation to a team. -type GrantToTeamRequest struct { +// GrantRoleRequest grants one role to exactly one person or team. +type GrantRoleRequest struct { state protoimpl.MessageState `protogen:"open.v1"` + // The organization the installation belongs to; a request naming another + // organization's installation is refused naming the organization, before + // the caller's authority is read. + OrganizationId string `protobuf:"bytes,1,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` // The installation. - InstallationId string `protobuf:"bytes,1,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` - // The team granted the installation's role at its root. - TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` - // The organisation the installation belongs to; a grant is an organisation - // administrator's act, and the host checks the actor against it before it - // reads the installation. Learned from the second implementation (the host). - OrganizationId string `protobuf:"bytes,3,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` + // One of the roles the solution declares. + Role string `protobuf:"bytes,3,opt,name=role,proto3" json:"role,omitempty"` + // The person granted, a member of the organization; exactly one of + // person_id and team_id is set. + PersonId string `protobuf:"bytes,4,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + // The team granted, a team of the organization. + TeamId string `protobuf:"bytes,5,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GrantToTeamRequest) Reset() { - *x = GrantToTeamRequest{} +func (x *GrantRoleRequest) Reset() { + *x = GrantRoleRequest{} mi := &file_host_installation_v1_installation_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GrantToTeamRequest) String() string { +func (x *GrantRoleRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GrantToTeamRequest) ProtoMessage() {} +func (*GrantRoleRequest) ProtoMessage() {} -func (x *GrantToTeamRequest) ProtoReflect() protoreflect.Message { +func (x *GrantRoleRequest) ProtoReflect() protoreflect.Message { mi := &file_host_installation_v1_installation_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -196,32 +203,293 @@ func (x *GrantToTeamRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GrantToTeamRequest.ProtoReflect.Descriptor instead. -func (*GrantToTeamRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GrantRoleRequest.ProtoReflect.Descriptor instead. +func (*GrantRoleRequest) Descriptor() ([]byte, []int) { return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{1} } -func (x *GrantToTeamRequest) GetInstallationId() string { +func (x *GrantRoleRequest) GetOrganizationId() string { + if x != nil { + return x.OrganizationId + } + return "" +} + +func (x *GrantRoleRequest) GetInstallationId() string { + if x != nil { + return x.InstallationId + } + return "" +} + +func (x *GrantRoleRequest) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *GrantRoleRequest) GetPersonId() string { + if x != nil { + return x.PersonId + } + return "" +} + +func (x *GrantRoleRequest) GetTeamId() string { + if x != nil { + return x.TeamId + } + return "" +} + +// RevokeRoleRequest ends one grant, named as GrantRole named it. +type RevokeRoleRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The organization the installation belongs to. + OrganizationId string `protobuf:"bytes,1,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` + // The installation. + InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` + // The role. + Role string `protobuf:"bytes,3,opt,name=role,proto3" json:"role,omitempty"` + // The person; exactly one of person_id and team_id is set. + PersonId string `protobuf:"bytes,4,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + // The team. + TeamId string `protobuf:"bytes,5,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeRoleRequest) Reset() { + *x = RevokeRoleRequest{} + mi := &file_host_installation_v1_installation_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeRoleRequest) ProtoMessage() {} + +func (x *RevokeRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeRoleRequest.ProtoReflect.Descriptor instead. +func (*RevokeRoleRequest) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{2} +} + +func (x *RevokeRoleRequest) GetOrganizationId() string { + if x != nil { + return x.OrganizationId + } + return "" +} + +func (x *RevokeRoleRequest) GetInstallationId() string { if x != nil { return x.InstallationId } return "" } -func (x *GrantToTeamRequest) GetTeamId() string { +func (x *RevokeRoleRequest) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *RevokeRoleRequest) GetPersonId() string { + if x != nil { + return x.PersonId + } + return "" +} + +func (x *RevokeRoleRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } -func (x *GrantToTeamRequest) GetOrganizationId() string { +// ListRoleGrantsRequest names one installation. +type ListRoleGrantsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The organization the installation belongs to. + OrganizationId string `protobuf:"bytes,1,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` + // The installation. + InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRoleGrantsRequest) Reset() { + *x = ListRoleGrantsRequest{} + mi := &file_host_installation_v1_installation_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoleGrantsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleGrantsRequest) ProtoMessage() {} + +func (x *ListRoleGrantsRequest) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleGrantsRequest.ProtoReflect.Descriptor instead. +func (*ListRoleGrantsRequest) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{3} +} + +func (x *ListRoleGrantsRequest) GetOrganizationId() string { if x != nil { return x.OrganizationId } return "" } +func (x *ListRoleGrantsRequest) GetInstallationId() string { + if x != nil { + return x.InstallationId + } + return "" +} + +// ListRoleGrantsResponse is every grant on the installation. +type ListRoleGrantsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The grants. + Grants []*RoleGrant `protobuf:"bytes,1,rep,name=grants,proto3" json:"grants,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRoleGrantsResponse) Reset() { + *x = ListRoleGrantsResponse{} + mi := &file_host_installation_v1_installation_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoleGrantsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleGrantsResponse) ProtoMessage() {} + +func (x *ListRoleGrantsResponse) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleGrantsResponse.ProtoReflect.Descriptor instead. +func (*ListRoleGrantsResponse) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{4} +} + +func (x *ListRoleGrantsResponse) GetGrants() []*RoleGrant { + if x != nil { + return x.Grants + } + return nil +} + +// RoleGrant is one role held by one person or team. +type RoleGrant struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The role. + Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + // The person, when a person holds it. + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + // The team, when a team holds it. + TeamId string `protobuf:"bytes,3,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoleGrant) Reset() { + *x = RoleGrant{} + mi := &file_host_installation_v1_installation_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoleGrant) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoleGrant) ProtoMessage() {} + +func (x *RoleGrant) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoleGrant.ProtoReflect.Descriptor instead. +func (*RoleGrant) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{5} +} + +func (x *RoleGrant) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *RoleGrant) GetPersonId() string { + if x != nil { + return x.PersonId + } + return "" +} + +func (x *RoleGrant) GetTeamId() string { + if x != nil { + return x.TeamId + } + return "" +} + // ListRequest asks for one organisation's installations. type ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -233,7 +501,7 @@ type ListRequest struct { func (x *ListRequest) Reset() { *x = ListRequest{} - mi := &file_host_installation_v1_installation_proto_msgTypes[2] + mi := &file_host_installation_v1_installation_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -245,7 +513,7 @@ func (x *ListRequest) String() string { func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[2] + mi := &file_host_installation_v1_installation_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -258,7 +526,7 @@ func (x *ListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. func (*ListRequest) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{2} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{6} } func (x *ListRequest) GetOrganizationId() string { @@ -279,7 +547,7 @@ type ListResponse struct { func (x *ListResponse) Reset() { *x = ListResponse{} - mi := &file_host_installation_v1_installation_proto_msgTypes[3] + mi := &file_host_installation_v1_installation_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -291,7 +559,7 @@ func (x *ListResponse) String() string { func (*ListResponse) ProtoMessage() {} func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[3] + mi := &file_host_installation_v1_installation_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -304,7 +572,7 @@ func (x *ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. func (*ListResponse) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{3} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{7} } func (x *ListResponse) GetInstallations() []*Installation { @@ -327,24 +595,27 @@ type Installation struct { Status Installation_Status `protobuf:"varint,4,opt,name=status,proto3,enum=host.installation.v1.Installation_Status" json:"status,omitempty"` // Bumped on every change that narrows or widens the installation. Revision uint64 `protobuf:"varint,5,opt,name=revision,proto3" json:"revision,omitempty"` - // The role the installation holds. + // The role the installation holds when it acts for itself (its agent's + // role); no person or team inherits it. Role string `protobuf:"bytes,6,opt,name=role,proto3" json:"role,omitempty"` - // The teams granted it. - TeamIds []string `protobuf:"bytes,7,rep,name=team_ids,json=teamIds,proto3" json:"team_ids,omitempty"` - // The principal the installation acts as for itself. - AgentPrincipalId string `protobuf:"bytes,8,opt,name=agent_principal_id,json=agentPrincipalId,proto3" json:"agent_principal_id,omitempty"` // The namespace the installation's own authored content belongs to (an // agent's source): the host names it, so a module never chooses which source // an installation may run. A module installed by composition is // ".". - Namespace string `protobuf:"bytes,9,opt,name=namespace,proto3" json:"namespace,omitempty"` + Namespace string `protobuf:"bytes,9,opt,name=namespace,proto3" json:"namespace,omitempty"` + // The solution principal: the identity the installation's data is owned by + // and the installation acts as for itself, a stable UUID per organization + // and target, reused by a reinstall. Empty for a module installation. + PrincipalId string `protobuf:"bytes,10,opt,name=principal_id,json=principalId,proto3" json:"principal_id,omitempty"` + // The roles the solution declares, sorted. + Roles []string `protobuf:"bytes,11,rep,name=roles,proto3" json:"roles,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Installation) Reset() { *x = Installation{} - mi := &file_host_installation_v1_installation_proto_msgTypes[4] + mi := &file_host_installation_v1_installation_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -356,7 +627,7 @@ func (x *Installation) String() string { func (*Installation) ProtoMessage() {} func (x *Installation) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[4] + mi := &file_host_installation_v1_installation_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -369,7 +640,7 @@ func (x *Installation) ProtoReflect() protoreflect.Message { // Deprecated: Use Installation.ProtoReflect.Descriptor instead. func (*Installation) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{4} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{8} } func (x *Installation) GetId() string { @@ -414,25 +685,25 @@ func (x *Installation) GetRole() string { return "" } -func (x *Installation) GetTeamIds() []string { +func (x *Installation) GetNamespace() string { if x != nil { - return x.TeamIds + return x.Namespace } - return nil + return "" } -func (x *Installation) GetAgentPrincipalId() string { +func (x *Installation) GetPrincipalId() string { if x != nil { - return x.AgentPrincipalId + return x.PrincipalId } return "" } -func (x *Installation) GetNamespace() string { +func (x *Installation) GetRoles() []string { if x != nil { - return x.Namespace + return x.Roles } - return "" + return nil } // InstallationNotice is what the host hands the solution's install step. @@ -446,7 +717,7 @@ type InstallationNotice struct { func (x *InstallationNotice) Reset() { *x = InstallationNotice{} - mi := &file_host_installation_v1_installation_proto_msgTypes[5] + mi := &file_host_installation_v1_installation_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -458,7 +729,7 @@ func (x *InstallationNotice) String() string { func (*InstallationNotice) ProtoMessage() {} func (x *InstallationNotice) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[5] + mi := &file_host_installation_v1_installation_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -471,7 +742,7 @@ func (x *InstallationNotice) ProtoReflect() protoreflect.Message { // Deprecated: Use InstallationNotice.ProtoReflect.Descriptor instead. func (*InstallationNotice) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{5} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{9} } func (x *InstallationNotice) GetInstallation() *Installation { @@ -490,7 +761,7 @@ type InstallationAck struct { func (x *InstallationAck) Reset() { *x = InstallationAck{} - mi := &file_host_installation_v1_installation_proto_msgTypes[6] + mi := &file_host_installation_v1_installation_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -502,7 +773,7 @@ func (x *InstallationAck) String() string { func (*InstallationAck) ProtoMessage() {} func (x *InstallationAck) ProtoReflect() protoreflect.Message { - mi := &file_host_installation_v1_installation_proto_msgTypes[6] + mi := &file_host_installation_v1_installation_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -515,7 +786,7 @@ func (x *InstallationAck) ProtoReflect() protoreflect.Message { // Deprecated: Use InstallationAck.ProtoReflect.Descriptor instead. func (*InstallationAck) Descriptor() ([]byte, []int) { - return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{6} + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{10} } var File_host_installation_v1_installation_proto protoreflect.FileDescriptor @@ -527,36 +798,57 @@ const file_host_installation_v1_installation_proto_rawDesc = "" + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12\x16\n" + "\x06target\x18\x02 \x01(\tR\x06target\x12\x12\n" + "\x04role\x18\x03 \x01(\tR\x04role\x12&\n" + - "\x0fowner_person_id\x18\x04 \x01(\tR\rownerPersonId\"\x7f\n" + - "\x12GrantToTeamRequest\x12'\n" + - "\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\x12\x17\n" + - "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12'\n" + - "\x0forganization_id\x18\x03 \x01(\tR\x0eorganizationId\"6\n" + + "\x0fowner_person_id\x18\x04 \x01(\tR\rownerPersonId\"\xae\x01\n" + + "\x10GrantRoleRequest\x12'\n" + + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12'\n" + + "\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\x12\x12\n" + + "\x04role\x18\x03 \x01(\tR\x04role\x12\x1b\n" + + "\tperson_id\x18\x04 \x01(\tR\bpersonId\x12\x17\n" + + "\ateam_id\x18\x05 \x01(\tR\x06teamId\"\xaf\x01\n" + + "\x11RevokeRoleRequest\x12'\n" + + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12'\n" + + "\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\x12\x12\n" + + "\x04role\x18\x03 \x01(\tR\x04role\x12\x1b\n" + + "\tperson_id\x18\x04 \x01(\tR\bpersonId\x12\x17\n" + + "\ateam_id\x18\x05 \x01(\tR\x06teamId\"i\n" + + "\x15ListRoleGrantsRequest\x12'\n" + + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\x12'\n" + + "\x0finstallation_id\x18\x02 \x01(\tR\x0einstallationId\"Q\n" + + "\x16ListRoleGrantsResponse\x127\n" + + "\x06grants\x18\x01 \x03(\v2\x1f.host.installation.v1.RoleGrantR\x06grants\"U\n" + + "\tRoleGrant\x12\x12\n" + + "\x04role\x18\x01 \x01(\tR\x04role\x12\x1b\n" + + "\tperson_id\x18\x02 \x01(\tR\bpersonId\x12\x17\n" + + "\ateam_id\x18\x03 \x01(\tR\x06teamId\"6\n" + "\vListRequest\x12'\n" + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\"X\n" + "\fListResponse\x12H\n" + - "\rinstallations\x18\x01 \x03(\v2\".host.installation.v1.InstallationR\rinstallations\"\x96\x03\n" + + "\rinstallations\x18\x01 \x03(\v2\".host.installation.v1.InstallationR\rinstallations\"\xb0\x03\n" + "\fInstallation\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12'\n" + "\x0forganization_id\x18\x02 \x01(\tR\x0eorganizationId\x12\x16\n" + "\x06target\x18\x03 \x01(\tR\x06target\x12A\n" + "\x06status\x18\x04 \x01(\x0e2).host.installation.v1.Installation.StatusR\x06status\x12\x1a\n" + "\brevision\x18\x05 \x01(\x04R\brevision\x12\x12\n" + - "\x04role\x18\x06 \x01(\tR\x04role\x12\x19\n" + - "\bteam_ids\x18\a \x03(\tR\ateamIds\x12,\n" + - "\x12agent_principal_id\x18\b \x01(\tR\x10agentPrincipalId\x12\x1c\n" + - "\tnamespace\x18\t \x01(\tR\tnamespace\"[\n" + + "\x04role\x18\x06 \x01(\tR\x04role\x12\x1c\n" + + "\tnamespace\x18\t \x01(\tR\tnamespace\x12!\n" + + "\fprincipal_id\x18\n" + + " \x01(\tR\vprincipalId\x12\x14\n" + + "\x05roles\x18\v \x03(\tR\x05roles\"[\n" + "\x06Status\x12\x16\n" + "\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n" + "\x0eSTATUS_PENDING\x10\x01\x12\x11\n" + "\rSTATUS_ACTIVE\x10\x02\x12\x12\n" + - "\x0eSTATUS_REVOKED\x10\x03\"\\\n" + + "\x0eSTATUS_REVOKED\x10\x03J\x04\b\a\x10\bJ\x04\b\b\x10\tR\bteam_idsR\x12agent_principal_id\"\\\n" + "\x12InstallationNotice\x12F\n" + "\finstallation\x18\x01 \x01(\v2\".host.installation.v1.InstallationR\finstallation\"\x11\n" + - "\x0fInstallationAck2\x96\x02\n" + + "\x0fInstallationAck2\xda\x03\n" + "\x13InstallationService\x12S\n" + - "\aInstall\x12$.host.installation.v1.InstallRequest\x1a\".host.installation.v1.Installation\x12[\n" + - "\vGrantToTeam\x12(.host.installation.v1.GrantToTeamRequest\x1a\".host.installation.v1.Installation\x12M\n" + + "\aInstall\x12$.host.installation.v1.InstallRequest\x1a\".host.installation.v1.Installation\x12W\n" + + "\tGrantRole\x12&.host.installation.v1.GrantRoleRequest\x1a\".host.installation.v1.Installation\x12Y\n" + + "\n" + + "RevokeRole\x12'.host.installation.v1.RevokeRoleRequest\x1a\".host.installation.v1.Installation\x12k\n" + + "\x0eListRoleGrants\x12+.host.installation.v1.ListRoleGrantsRequest\x1a,.host.installation.v1.ListRoleGrantsResponse\x12M\n" + "\x04List\x12!.host.installation.v1.ListRequest\x1a\".host.installation.v1.ListResponse2p\n" + "\x12InstallableService\x12Z\n" + "\aInstall\x12(.host.installation.v1.InstallationNotice\x1a%.host.installation.v1.InstallationAckB5Z3gateway/pkg/gen/host/installation/v1;installationv1b\x06proto3" @@ -574,34 +866,43 @@ func file_host_installation_v1_installation_proto_rawDescGZIP() []byte { } var file_host_installation_v1_installation_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_host_installation_v1_installation_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_host_installation_v1_installation_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_host_installation_v1_installation_proto_goTypes = []any{ - (Installation_Status)(0), // 0: host.installation.v1.Installation.Status - (*InstallRequest)(nil), // 1: host.installation.v1.InstallRequest - (*GrantToTeamRequest)(nil), // 2: host.installation.v1.GrantToTeamRequest - (*ListRequest)(nil), // 3: host.installation.v1.ListRequest - (*ListResponse)(nil), // 4: host.installation.v1.ListResponse - (*Installation)(nil), // 5: host.installation.v1.Installation - (*InstallationNotice)(nil), // 6: host.installation.v1.InstallationNotice - (*InstallationAck)(nil), // 7: host.installation.v1.InstallationAck + (Installation_Status)(0), // 0: host.installation.v1.Installation.Status + (*InstallRequest)(nil), // 1: host.installation.v1.InstallRequest + (*GrantRoleRequest)(nil), // 2: host.installation.v1.GrantRoleRequest + (*RevokeRoleRequest)(nil), // 3: host.installation.v1.RevokeRoleRequest + (*ListRoleGrantsRequest)(nil), // 4: host.installation.v1.ListRoleGrantsRequest + (*ListRoleGrantsResponse)(nil), // 5: host.installation.v1.ListRoleGrantsResponse + (*RoleGrant)(nil), // 6: host.installation.v1.RoleGrant + (*ListRequest)(nil), // 7: host.installation.v1.ListRequest + (*ListResponse)(nil), // 8: host.installation.v1.ListResponse + (*Installation)(nil), // 9: host.installation.v1.Installation + (*InstallationNotice)(nil), // 10: host.installation.v1.InstallationNotice + (*InstallationAck)(nil), // 11: host.installation.v1.InstallationAck } var file_host_installation_v1_installation_proto_depIdxs = []int32{ - 5, // 0: host.installation.v1.ListResponse.installations:type_name -> host.installation.v1.Installation - 0, // 1: host.installation.v1.Installation.status:type_name -> host.installation.v1.Installation.Status - 5, // 2: host.installation.v1.InstallationNotice.installation:type_name -> host.installation.v1.Installation - 1, // 3: host.installation.v1.InstallationService.Install:input_type -> host.installation.v1.InstallRequest - 2, // 4: host.installation.v1.InstallationService.GrantToTeam:input_type -> host.installation.v1.GrantToTeamRequest - 3, // 5: host.installation.v1.InstallationService.List:input_type -> host.installation.v1.ListRequest - 6, // 6: host.installation.v1.InstallableService.Install:input_type -> host.installation.v1.InstallationNotice - 5, // 7: host.installation.v1.InstallationService.Install:output_type -> host.installation.v1.Installation - 5, // 8: host.installation.v1.InstallationService.GrantToTeam:output_type -> host.installation.v1.Installation - 4, // 9: host.installation.v1.InstallationService.List:output_type -> host.installation.v1.ListResponse - 7, // 10: host.installation.v1.InstallableService.Install:output_type -> host.installation.v1.InstallationAck - 7, // [7:11] is the sub-list for method output_type - 3, // [3:7] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 6, // 0: host.installation.v1.ListRoleGrantsResponse.grants:type_name -> host.installation.v1.RoleGrant + 9, // 1: host.installation.v1.ListResponse.installations:type_name -> host.installation.v1.Installation + 0, // 2: host.installation.v1.Installation.status:type_name -> host.installation.v1.Installation.Status + 9, // 3: host.installation.v1.InstallationNotice.installation:type_name -> host.installation.v1.Installation + 1, // 4: host.installation.v1.InstallationService.Install:input_type -> host.installation.v1.InstallRequest + 2, // 5: host.installation.v1.InstallationService.GrantRole:input_type -> host.installation.v1.GrantRoleRequest + 3, // 6: host.installation.v1.InstallationService.RevokeRole:input_type -> host.installation.v1.RevokeRoleRequest + 4, // 7: host.installation.v1.InstallationService.ListRoleGrants:input_type -> host.installation.v1.ListRoleGrantsRequest + 7, // 8: host.installation.v1.InstallationService.List:input_type -> host.installation.v1.ListRequest + 10, // 9: host.installation.v1.InstallableService.Install:input_type -> host.installation.v1.InstallationNotice + 9, // 10: host.installation.v1.InstallationService.Install:output_type -> host.installation.v1.Installation + 9, // 11: host.installation.v1.InstallationService.GrantRole:output_type -> host.installation.v1.Installation + 9, // 12: host.installation.v1.InstallationService.RevokeRole:output_type -> host.installation.v1.Installation + 5, // 13: host.installation.v1.InstallationService.ListRoleGrants:output_type -> host.installation.v1.ListRoleGrantsResponse + 8, // 14: host.installation.v1.InstallationService.List:output_type -> host.installation.v1.ListResponse + 11, // 15: host.installation.v1.InstallableService.Install:output_type -> host.installation.v1.InstallationAck + 10, // [10:16] is the sub-list for method output_type + 4, // [4:10] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_host_installation_v1_installation_proto_init() } @@ -615,7 +916,7 @@ func file_host_installation_v1_installation_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_host_installation_v1_installation_proto_rawDesc), len(file_host_installation_v1_installation_proto_rawDesc)), NumEnums: 1, - NumMessages: 7, + NumMessages: 11, NumExtensions: 0, NumServices: 2, }, diff --git a/modules/authority/services/local/code/pkg/gen/host/installation/v1/installation_grpc.pb.go b/modules/authority/services/local/code/pkg/gen/host/installation/v1/installation_grpc.pb.go index 8d5c6fea..d6a5f8f9 100644 --- a/modules/authority/services/local/code/pkg/gen/host/installation/v1/installation_grpc.pb.go +++ b/modules/authority/services/local/code/pkg/gen/host/installation/v1/installation_grpc.pb.go @@ -5,8 +5,10 @@ // source: host/installation/v1/installation.proto // The installation interface: what is installed at an organisation and who may -// use it. Three operations on the host, and one the solution implements, the -// install step the host calls once per installation. +// use it. An installed solution is a principal of its organisation; its roles +// are levels on the data that principal owns, and a grant of a role is the +// access. Operations on the host, and one the solution implements, the install +// step the host calls once per installation. package installationv1 @@ -24,9 +26,11 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - InstallationService_Install_FullMethodName = "/host.installation.v1.InstallationService/Install" - InstallationService_GrantToTeam_FullMethodName = "/host.installation.v1.InstallationService/GrantToTeam" - InstallationService_List_FullMethodName = "/host.installation.v1.InstallationService/List" + InstallationService_Install_FullMethodName = "/host.installation.v1.InstallationService/Install" + InstallationService_GrantRole_FullMethodName = "/host.installation.v1.InstallationService/GrantRole" + InstallationService_RevokeRole_FullMethodName = "/host.installation.v1.InstallationService/RevokeRole" + InstallationService_ListRoleGrants_FullMethodName = "/host.installation.v1.InstallationService/ListRoleGrants" + InstallationService_List_FullMethodName = "/host.installation.v1.InstallationService/List" ) // InstallationServiceClient is the client API for InstallationService service. @@ -38,8 +42,17 @@ type InstallationServiceClient interface { // Install composes an installation: its agent principal, its root scope and // the row, pending until the target acknowledges its install step. Install(ctx context.Context, in *InstallRequest, opts ...grpc.CallOption) (*Installation, error) - // GrantToTeam exposes an installation to a team under the installation's role. - GrantToTeam(ctx context.Context, in *GrantToTeamRequest, opts ...grpc.CallOption) (*Installation, error) + // GrantRole grants a person or a team one of the solution's roles on the + // installation: the access. Idempotent; it leaves existing capabilities + // valid. An administrator of the installation's organization, or a holder + // of the installer role on that installation, grants. + GrantRole(ctx context.Context, in *GrantRoleRequest, opts ...grpc.CallOption) (*Installation, error) + // RevokeRole ends one grant; every capability its subject holds from it is + // refused at once. Idempotent; the same callers as GrantRole. + RevokeRole(ctx context.Context, in *RevokeRoleRequest, opts ...grpc.CallOption) (*Installation, error) + // ListRoleGrants answers who holds which role on the installation; the same + // callers as GrantRole. + ListRoleGrants(ctx context.Context, in *ListRoleGrantsRequest, opts ...grpc.CallOption) (*ListRoleGrantsResponse, error) // List answers what one organisation has installed: an administrator's read. A // member's view, which installed solutions they may use, is their navigation and // belongs to the entitlement read, not here. @@ -64,10 +77,30 @@ func (c *installationServiceClient) Install(ctx context.Context, in *InstallRequ return out, nil } -func (c *installationServiceClient) GrantToTeam(ctx context.Context, in *GrantToTeamRequest, opts ...grpc.CallOption) (*Installation, error) { +func (c *installationServiceClient) GrantRole(ctx context.Context, in *GrantRoleRequest, opts ...grpc.CallOption) (*Installation, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Installation) - err := c.cc.Invoke(ctx, InstallationService_GrantToTeam_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, InstallationService_GrantRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *installationServiceClient) RevokeRole(ctx context.Context, in *RevokeRoleRequest, opts ...grpc.CallOption) (*Installation, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Installation) + err := c.cc.Invoke(ctx, InstallationService_RevokeRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *installationServiceClient) ListRoleGrants(ctx context.Context, in *ListRoleGrantsRequest, opts ...grpc.CallOption) (*ListRoleGrantsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRoleGrantsResponse) + err := c.cc.Invoke(ctx, InstallationService_ListRoleGrants_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -93,8 +126,17 @@ type InstallationServiceServer interface { // Install composes an installation: its agent principal, its root scope and // the row, pending until the target acknowledges its install step. Install(context.Context, *InstallRequest) (*Installation, error) - // GrantToTeam exposes an installation to a team under the installation's role. - GrantToTeam(context.Context, *GrantToTeamRequest) (*Installation, error) + // GrantRole grants a person or a team one of the solution's roles on the + // installation: the access. Idempotent; it leaves existing capabilities + // valid. An administrator of the installation's organization, or a holder + // of the installer role on that installation, grants. + GrantRole(context.Context, *GrantRoleRequest) (*Installation, error) + // RevokeRole ends one grant; every capability its subject holds from it is + // refused at once. Idempotent; the same callers as GrantRole. + RevokeRole(context.Context, *RevokeRoleRequest) (*Installation, error) + // ListRoleGrants answers who holds which role on the installation; the same + // callers as GrantRole. + ListRoleGrants(context.Context, *ListRoleGrantsRequest) (*ListRoleGrantsResponse, error) // List answers what one organisation has installed: an administrator's read. A // member's view, which installed solutions they may use, is their navigation and // belongs to the entitlement read, not here. @@ -112,8 +154,14 @@ type UnimplementedInstallationServiceServer struct{} func (UnimplementedInstallationServiceServer) Install(context.Context, *InstallRequest) (*Installation, error) { return nil, status.Error(codes.Unimplemented, "method Install not implemented") } -func (UnimplementedInstallationServiceServer) GrantToTeam(context.Context, *GrantToTeamRequest) (*Installation, error) { - return nil, status.Error(codes.Unimplemented, "method GrantToTeam not implemented") +func (UnimplementedInstallationServiceServer) GrantRole(context.Context, *GrantRoleRequest) (*Installation, error) { + return nil, status.Error(codes.Unimplemented, "method GrantRole not implemented") +} +func (UnimplementedInstallationServiceServer) RevokeRole(context.Context, *RevokeRoleRequest) (*Installation, error) { + return nil, status.Error(codes.Unimplemented, "method RevokeRole not implemented") +} +func (UnimplementedInstallationServiceServer) ListRoleGrants(context.Context, *ListRoleGrantsRequest) (*ListRoleGrantsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListRoleGrants not implemented") } func (UnimplementedInstallationServiceServer) List(context.Context, *ListRequest) (*ListResponse, error) { return nil, status.Error(codes.Unimplemented, "method List not implemented") @@ -157,20 +205,56 @@ func _InstallationService_Install_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _InstallationService_GrantToTeam_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GrantToTeamRequest) +func _InstallationService_GrantRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GrantRoleRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(InstallationServiceServer).GrantToTeam(ctx, in) + return srv.(InstallationServiceServer).GrantRole(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: InstallationService_GrantToTeam_FullMethodName, + FullMethod: InstallationService_GrantRole_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(InstallationServiceServer).GrantToTeam(ctx, req.(*GrantToTeamRequest)) + return srv.(InstallationServiceServer).GrantRole(ctx, req.(*GrantRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InstallationService_RevokeRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InstallationServiceServer).RevokeRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InstallationService_RevokeRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InstallationServiceServer).RevokeRole(ctx, req.(*RevokeRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InstallationService_ListRoleGrants_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRoleGrantsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InstallationServiceServer).ListRoleGrants(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InstallationService_ListRoleGrants_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InstallationServiceServer).ListRoleGrants(ctx, req.(*ListRoleGrantsRequest)) } return interceptor(ctx, in, info, handler) } @@ -205,8 +289,16 @@ var InstallationService_ServiceDesc = grpc.ServiceDesc{ Handler: _InstallationService_Install_Handler, }, { - MethodName: "GrantToTeam", - Handler: _InstallationService_GrantToTeam_Handler, + MethodName: "GrantRole", + Handler: _InstallationService_GrantRole_Handler, + }, + { + MethodName: "RevokeRole", + Handler: _InstallationService_RevokeRole_Handler, + }, + { + MethodName: "ListRoleGrants", + Handler: _InstallationService_ListRoleGrants_Handler, }, { MethodName: "List", diff --git a/modules/authority/services/local/code/pkg/gen/host/installation/v1/installationv1connect/installation.connect.go b/modules/authority/services/local/code/pkg/gen/host/installation/v1/installationv1connect/installation.connect.go index 8084ac77..71008349 100644 --- a/modules/authority/services/local/code/pkg/gen/host/installation/v1/installationv1connect/installation.connect.go +++ b/modules/authority/services/local/code/pkg/gen/host/installation/v1/installationv1connect/installation.connect.go @@ -3,8 +3,10 @@ // Source: host/installation/v1/installation.proto // The installation interface: what is installed at an organisation and who may -// use it. Three operations on the host, and one the solution implements, the -// install step the host calls once per installation. +// use it. An installed solution is a principal of its organisation; its roles +// are levels on the data that principal owns, and a grant of a role is the +// access. Operations on the host, and one the solution implements, the install +// step the host calls once per installation. package installationv1connect import ( @@ -42,9 +44,15 @@ const ( // InstallationServiceInstallProcedure is the fully-qualified name of the InstallationService's // Install RPC. InstallationServiceInstallProcedure = "/host.installation.v1.InstallationService/Install" - // InstallationServiceGrantToTeamProcedure is the fully-qualified name of the InstallationService's - // GrantToTeam RPC. - InstallationServiceGrantToTeamProcedure = "/host.installation.v1.InstallationService/GrantToTeam" + // InstallationServiceGrantRoleProcedure is the fully-qualified name of the InstallationService's + // GrantRole RPC. + InstallationServiceGrantRoleProcedure = "/host.installation.v1.InstallationService/GrantRole" + // InstallationServiceRevokeRoleProcedure is the fully-qualified name of the InstallationService's + // RevokeRole RPC. + InstallationServiceRevokeRoleProcedure = "/host.installation.v1.InstallationService/RevokeRole" + // InstallationServiceListRoleGrantsProcedure is the fully-qualified name of the + // InstallationService's ListRoleGrants RPC. + InstallationServiceListRoleGrantsProcedure = "/host.installation.v1.InstallationService/ListRoleGrants" // InstallationServiceListProcedure is the fully-qualified name of the InstallationService's List // RPC. InstallationServiceListProcedure = "/host.installation.v1.InstallationService/List" @@ -58,8 +66,17 @@ type InstallationServiceClient interface { // Install composes an installation: its agent principal, its root scope and // the row, pending until the target acknowledges its install step. Install(context.Context, *connect.Request[v1.InstallRequest]) (*connect.Response[v1.Installation], error) - // GrantToTeam exposes an installation to a team under the installation's role. - GrantToTeam(context.Context, *connect.Request[v1.GrantToTeamRequest]) (*connect.Response[v1.Installation], error) + // GrantRole grants a person or a team one of the solution's roles on the + // installation: the access. Idempotent; it leaves existing capabilities + // valid. An administrator of the installation's organization, or a holder + // of the installer role on that installation, grants. + GrantRole(context.Context, *connect.Request[v1.GrantRoleRequest]) (*connect.Response[v1.Installation], error) + // RevokeRole ends one grant; every capability its subject holds from it is + // refused at once. Idempotent; the same callers as GrantRole. + RevokeRole(context.Context, *connect.Request[v1.RevokeRoleRequest]) (*connect.Response[v1.Installation], error) + // ListRoleGrants answers who holds which role on the installation; the same + // callers as GrantRole. + ListRoleGrants(context.Context, *connect.Request[v1.ListRoleGrantsRequest]) (*connect.Response[v1.ListRoleGrantsResponse], error) // List answers what one organisation has installed: an administrator's read. A // member's view, which installed solutions they may use, is their navigation and // belongs to the entitlement read, not here. @@ -83,10 +100,22 @@ func NewInstallationServiceClient(httpClient connect.HTTPClient, baseURL string, connect.WithSchema(installationServiceMethods.ByName("Install")), connect.WithClientOptions(opts...), ), - grantToTeam: connect.NewClient[v1.GrantToTeamRequest, v1.Installation]( + grantRole: connect.NewClient[v1.GrantRoleRequest, v1.Installation]( httpClient, - baseURL+InstallationServiceGrantToTeamProcedure, - connect.WithSchema(installationServiceMethods.ByName("GrantToTeam")), + baseURL+InstallationServiceGrantRoleProcedure, + connect.WithSchema(installationServiceMethods.ByName("GrantRole")), + connect.WithClientOptions(opts...), + ), + revokeRole: connect.NewClient[v1.RevokeRoleRequest, v1.Installation]( + httpClient, + baseURL+InstallationServiceRevokeRoleProcedure, + connect.WithSchema(installationServiceMethods.ByName("RevokeRole")), + connect.WithClientOptions(opts...), + ), + listRoleGrants: connect.NewClient[v1.ListRoleGrantsRequest, v1.ListRoleGrantsResponse]( + httpClient, + baseURL+InstallationServiceListRoleGrantsProcedure, + connect.WithSchema(installationServiceMethods.ByName("ListRoleGrants")), connect.WithClientOptions(opts...), ), list: connect.NewClient[v1.ListRequest, v1.ListResponse]( @@ -100,9 +129,11 @@ func NewInstallationServiceClient(httpClient connect.HTTPClient, baseURL string, // installationServiceClient implements InstallationServiceClient. type installationServiceClient struct { - install *connect.Client[v1.InstallRequest, v1.Installation] - grantToTeam *connect.Client[v1.GrantToTeamRequest, v1.Installation] - list *connect.Client[v1.ListRequest, v1.ListResponse] + install *connect.Client[v1.InstallRequest, v1.Installation] + grantRole *connect.Client[v1.GrantRoleRequest, v1.Installation] + revokeRole *connect.Client[v1.RevokeRoleRequest, v1.Installation] + listRoleGrants *connect.Client[v1.ListRoleGrantsRequest, v1.ListRoleGrantsResponse] + list *connect.Client[v1.ListRequest, v1.ListResponse] } // Install calls host.installation.v1.InstallationService.Install. @@ -110,9 +141,19 @@ func (c *installationServiceClient) Install(ctx context.Context, req *connect.Re return c.install.CallUnary(ctx, req) } -// GrantToTeam calls host.installation.v1.InstallationService.GrantToTeam. -func (c *installationServiceClient) GrantToTeam(ctx context.Context, req *connect.Request[v1.GrantToTeamRequest]) (*connect.Response[v1.Installation], error) { - return c.grantToTeam.CallUnary(ctx, req) +// GrantRole calls host.installation.v1.InstallationService.GrantRole. +func (c *installationServiceClient) GrantRole(ctx context.Context, req *connect.Request[v1.GrantRoleRequest]) (*connect.Response[v1.Installation], error) { + return c.grantRole.CallUnary(ctx, req) +} + +// RevokeRole calls host.installation.v1.InstallationService.RevokeRole. +func (c *installationServiceClient) RevokeRole(ctx context.Context, req *connect.Request[v1.RevokeRoleRequest]) (*connect.Response[v1.Installation], error) { + return c.revokeRole.CallUnary(ctx, req) +} + +// ListRoleGrants calls host.installation.v1.InstallationService.ListRoleGrants. +func (c *installationServiceClient) ListRoleGrants(ctx context.Context, req *connect.Request[v1.ListRoleGrantsRequest]) (*connect.Response[v1.ListRoleGrantsResponse], error) { + return c.listRoleGrants.CallUnary(ctx, req) } // List calls host.installation.v1.InstallationService.List. @@ -126,8 +167,17 @@ type InstallationServiceHandler interface { // Install composes an installation: its agent principal, its root scope and // the row, pending until the target acknowledges its install step. Install(context.Context, *connect.Request[v1.InstallRequest]) (*connect.Response[v1.Installation], error) - // GrantToTeam exposes an installation to a team under the installation's role. - GrantToTeam(context.Context, *connect.Request[v1.GrantToTeamRequest]) (*connect.Response[v1.Installation], error) + // GrantRole grants a person or a team one of the solution's roles on the + // installation: the access. Idempotent; it leaves existing capabilities + // valid. An administrator of the installation's organization, or a holder + // of the installer role on that installation, grants. + GrantRole(context.Context, *connect.Request[v1.GrantRoleRequest]) (*connect.Response[v1.Installation], error) + // RevokeRole ends one grant; every capability its subject holds from it is + // refused at once. Idempotent; the same callers as GrantRole. + RevokeRole(context.Context, *connect.Request[v1.RevokeRoleRequest]) (*connect.Response[v1.Installation], error) + // ListRoleGrants answers who holds which role on the installation; the same + // callers as GrantRole. + ListRoleGrants(context.Context, *connect.Request[v1.ListRoleGrantsRequest]) (*connect.Response[v1.ListRoleGrantsResponse], error) // List answers what one organisation has installed: an administrator's read. A // member's view, which installed solutions they may use, is their navigation and // belongs to the entitlement read, not here. @@ -147,10 +197,22 @@ func NewInstallationServiceHandler(svc InstallationServiceHandler, opts ...conne connect.WithSchema(installationServiceMethods.ByName("Install")), connect.WithHandlerOptions(opts...), ) - installationServiceGrantToTeamHandler := connect.NewUnaryHandler( - InstallationServiceGrantToTeamProcedure, - svc.GrantToTeam, - connect.WithSchema(installationServiceMethods.ByName("GrantToTeam")), + installationServiceGrantRoleHandler := connect.NewUnaryHandler( + InstallationServiceGrantRoleProcedure, + svc.GrantRole, + connect.WithSchema(installationServiceMethods.ByName("GrantRole")), + connect.WithHandlerOptions(opts...), + ) + installationServiceRevokeRoleHandler := connect.NewUnaryHandler( + InstallationServiceRevokeRoleProcedure, + svc.RevokeRole, + connect.WithSchema(installationServiceMethods.ByName("RevokeRole")), + connect.WithHandlerOptions(opts...), + ) + installationServiceListRoleGrantsHandler := connect.NewUnaryHandler( + InstallationServiceListRoleGrantsProcedure, + svc.ListRoleGrants, + connect.WithSchema(installationServiceMethods.ByName("ListRoleGrants")), connect.WithHandlerOptions(opts...), ) installationServiceListHandler := connect.NewUnaryHandler( @@ -163,8 +225,12 @@ func NewInstallationServiceHandler(svc InstallationServiceHandler, opts ...conne switch r.URL.Path { case InstallationServiceInstallProcedure: installationServiceInstallHandler.ServeHTTP(w, r) - case InstallationServiceGrantToTeamProcedure: - installationServiceGrantToTeamHandler.ServeHTTP(w, r) + case InstallationServiceGrantRoleProcedure: + installationServiceGrantRoleHandler.ServeHTTP(w, r) + case InstallationServiceRevokeRoleProcedure: + installationServiceRevokeRoleHandler.ServeHTTP(w, r) + case InstallationServiceListRoleGrantsProcedure: + installationServiceListRoleGrantsHandler.ServeHTTP(w, r) case InstallationServiceListProcedure: installationServiceListHandler.ServeHTTP(w, r) default: @@ -180,8 +246,16 @@ func (UnimplementedInstallationServiceHandler) Install(context.Context, *connect return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.installation.v1.InstallationService.Install is not implemented")) } -func (UnimplementedInstallationServiceHandler) GrantToTeam(context.Context, *connect.Request[v1.GrantToTeamRequest]) (*connect.Response[v1.Installation], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.installation.v1.InstallationService.GrantToTeam is not implemented")) +func (UnimplementedInstallationServiceHandler) GrantRole(context.Context, *connect.Request[v1.GrantRoleRequest]) (*connect.Response[v1.Installation], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.installation.v1.InstallationService.GrantRole is not implemented")) +} + +func (UnimplementedInstallationServiceHandler) RevokeRole(context.Context, *connect.Request[v1.RevokeRoleRequest]) (*connect.Response[v1.Installation], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.installation.v1.InstallationService.RevokeRole is not implemented")) +} + +func (UnimplementedInstallationServiceHandler) ListRoleGrants(context.Context, *connect.Request[v1.ListRoleGrantsRequest]) (*connect.Response[v1.ListRoleGrantsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.installation.v1.InstallationService.ListRoleGrants is not implemented")) } func (UnimplementedInstallationServiceHandler) List(context.Context, *connect.Request[v1.ListRequest]) (*connect.Response[v1.ListResponse], error) { diff --git a/modules/authority/services/local/openapi/host/installation/v1/installation.swagger.json b/modules/authority/services/local/openapi/host/installation/v1/installation.swagger.json index af1b3258..9804b34f 100644 --- a/modules/authority/services/local/openapi/host/installation/v1/installation.swagger.json +++ b/modules/authority/services/local/openapi/host/installation/v1/installation.swagger.json @@ -2,7 +2,7 @@ "swagger": "2.0", "info": { "title": "host/installation/v1/installation.proto", - "description": "The installation interface: what is installed at an organisation and who may\nuse it. Three operations on the host, and one the solution implements, the\ninstall step the host calls once per installation.", + "description": "The installation interface: what is installed at an organisation and who may\nuse it. An installed solution is a principal of its organisation; its roles\nare levels on the data that principal owns, and a grant of a role is the\naccess. Operations on the host, and one the solution implements, the install\nstep the host calls once per installation.", "version": "version not set" }, "tags": [ diff --git a/modules/authority/services/local/proto/host/authority/v1/authority.proto b/modules/authority/services/local/proto/host/authority/v1/authority.proto index ce8a6c75..0cf2ccb3 100644 --- a/modules/authority/services/local/proto/host/authority/v1/authority.proto +++ b/modules/authority/services/local/proto/host/authority/v1/authority.proto @@ -33,6 +33,19 @@ service AuthorityService { // rather than carried in the capability. A module maps the answer onto its // own resources; the authority knows them only by id. rpc ListResources(ListResourcesRequest) returns (ListResourcesResponse); + // AuthorizeTransfer decides whether an existing resource may join an + // installed solution's principal. The caller is the kind's one serving + // module, which attests the resource exists and who owns it now + // (from_owner_id), presenting the capability of the person asking: issued + // through the target principal's installation and addressed to the caller. + // It is allowed when the current owner is the organization or a person of + // it (never another solution principal, another organization or an unknown + // owner), the person holds admin on that resource from their own + // organization grants, and the person's current roles on the principal hold + // write on the kind, all read live. An installation acting for itself never + // transfers. The store then records the principal as owner; the original + // creator stays the author. + rpc AuthorizeTransfer(AuthorizeTransferRequest) returns (AuthorizeTransferResponse); // Delegate records a signed-in person's grant to a module (the caller's // bearer is the person); Issue with a delegation acts on it. rpc Delegate(DelegateRequest) returns (Delegation); @@ -244,7 +257,33 @@ message ListResourcesResponse { repeated ResourceGrant grants = 1; } -// ResourceGrant is one way of naming covered resources. Exactly one is set. +// AuthorizeTransferRequest names one existing resource and where it moves. +message AuthorizeTransferRequest { + // The capability of the person asking, addressed to the caller. + string token = 1; + // The module serving resource_kind, proving which module it is. + WorkloadCredential actor = 2; + // The resource kind, as the caller serves it. + string resource_kind = 3; + // The resource id, as the caller knows it. + string resource_id = 4; + // The resource's current owner, as the caller records it: the organization + // or a person of it. + string from_owner_id = 5; + // The solution principal the resource joins. + string to_principal_id = 6; +} + +// AuthorizeTransferResponse is the decision the store records. +message AuthorizeTransferResponse { + // The principal that now owns the resource. + string principal_id = 1; + // The person who authorized the transfer. + string authorized_by = 2; +} + +// ResourceGrant is one way of naming covered resources. Exactly one match is +// set; resource_owner_id, when set, qualifies it. message ResourceGrant { oneof match { // Every resource of the kind. @@ -257,4 +296,7 @@ message ResourceGrant { // bytes; it is matched against the entire id, as if anchored. string pattern = 4; } + // The owner the covered resources must have: a resource is covered when + // the match covers its id and it is owned by this principal. + string resource_owner_id = 5; } diff --git a/modules/authority/services/local/proto/host/composition/v1/composition.proto b/modules/authority/services/local/proto/host/composition/v1/composition.proto index a5085936..06bd518f 100644 --- a/modules/authority/services/local/proto/host/composition/v1/composition.proto +++ b/modules/authority/services/local/proto/host/composition/v1/composition.proto @@ -62,6 +62,11 @@ message AuthorityManifestV1 { // Core's manifest carries no roles, so they travel here until it does. A // module declares none; a module's delivery listing any is refused. repeated RoleV1 roles = 6; + // The module checks a resource's owner against an owner-selected scope + // (Claims.AllowsOwned): only an audience declaring it receives one, and a + // delivery withdrawing it refuses the owner-selected capabilities issued + // before. Every solution receiving its roles' scopes declares it. + bool owner_aware = 7; } // MethodPolicyV1 is what a call to one method must hold, as the module's @@ -112,7 +117,7 @@ message ModuleManifestV1 { PageV1 page = 7; // The roles a solution declares a person may hold on an installation of it, // each within what the solution serves or explicitly consumes through - // invoke/lookup bindings; one is named installer, held by whoever installs it. + // invoke/lookup bindings; one is named installer, held only by whom it is granted to. // Headless-only scopes cannot supply a role grant. A module declares none. repeated RoleV1 roles = 8; } diff --git a/modules/authority/services/local/proto/host/installation/v1/installation.proto b/modules/authority/services/local/proto/host/installation/v1/installation.proto index 7d7f129d..0898c683 100644 --- a/modules/authority/services/local/proto/host/installation/v1/installation.proto +++ b/modules/authority/services/local/proto/host/installation/v1/installation.proto @@ -1,7 +1,9 @@ syntax = "proto3"; // The installation interface: what is installed at an organisation and who may -// use it. Three operations on the host, and one the solution implements, the -// install step the host calls once per installation. +// use it. An installed solution is a principal of its organisation; its roles +// are levels on the data that principal owns, and a grant of a role is the +// access. Operations on the host, and one the solution implements, the install +// step the host calls once per installation. package host.installation.v1; option go_package = "gateway/pkg/gen/host/installation/v1;installationv1"; @@ -11,8 +13,17 @@ service InstallationService { // Install composes an installation: its agent principal, its root scope and // the row, pending until the target acknowledges its install step. rpc Install(InstallRequest) returns (Installation); - // GrantToTeam exposes an installation to a team under the installation's role. - rpc GrantToTeam(GrantToTeamRequest) returns (Installation); + // GrantRole grants a person or a team one of the solution's roles on the + // installation: the access. Idempotent; it leaves existing capabilities + // valid. An administrator of the installation's organization, or a holder + // of the installer role on that installation, grants. + rpc GrantRole(GrantRoleRequest) returns (Installation); + // RevokeRole ends one grant; every capability its subject holds from it is + // refused at once. Idempotent; the same callers as GrantRole. + rpc RevokeRole(RevokeRoleRequest) returns (Installation); + // ListRoleGrants answers who holds which role on the installation; the same + // callers as GrantRole. + rpc ListRoleGrants(ListRoleGrantsRequest) returns (ListRoleGrantsResponse); // List answers what one organisation has installed: an administrator's read. A // member's view, which installed solutions they may use, is their navigation and // belongs to the entitlement read, not here. @@ -38,16 +49,59 @@ message InstallRequest { string owner_person_id = 4; } -// GrantToTeamRequest exposes an installation to a team. -message GrantToTeamRequest { +// GrantRoleRequest grants one role to exactly one person or team. +message GrantRoleRequest { + // The organization the installation belongs to; a request naming another + // organization's installation is refused naming the organization, before + // the caller's authority is read. + string organization_id = 1; + // The installation. + string installation_id = 2; + // One of the roles the solution declares. + string role = 3; + // The person granted, a member of the organization; exactly one of + // person_id and team_id is set. + string person_id = 4; + // The team granted, a team of the organization. + string team_id = 5; +} + +// RevokeRoleRequest ends one grant, named as GrantRole named it. +message RevokeRoleRequest { + // The organization the installation belongs to. + string organization_id = 1; // The installation. - string installation_id = 1; - // The team granted the installation's role at its root. - string team_id = 2; - // The organisation the installation belongs to; a grant is an organisation - // administrator's act, and the host checks the actor against it before it - // reads the installation. Learned from the second implementation (the host). - string organization_id = 3; + string installation_id = 2; + // The role. + string role = 3; + // The person; exactly one of person_id and team_id is set. + string person_id = 4; + // The team. + string team_id = 5; +} + +// ListRoleGrantsRequest names one installation. +message ListRoleGrantsRequest { + // The organization the installation belongs to. + string organization_id = 1; + // The installation. + string installation_id = 2; +} + +// ListRoleGrantsResponse is every grant on the installation. +message ListRoleGrantsResponse { + // The grants. + repeated RoleGrant grants = 1; +} + +// RoleGrant is one role held by one person or team. +message RoleGrant { + // The role. + string role = 1; + // The person, when a person holds it. + string person_id = 2; + // The team, when a team holds it. + string team_id = 3; } // ListRequest asks for one organisation's installations. @@ -74,17 +128,22 @@ message Installation { Status status = 4; // Bumped on every change that narrows or widens the installation. uint64 revision = 5; - // The role the installation holds. + // The role the installation holds when it acts for itself (its agent's + // role); no person or team inherits it. string role = 6; - // The teams granted it. - repeated string team_ids = 7; - // The principal the installation acts as for itself. - string agent_principal_id = 8; + reserved 7, 8; + reserved "team_ids", "agent_principal_id"; // The namespace the installation's own authored content belongs to (an // agent's source): the host names it, so a module never chooses which source // an installation may run. A module installed by composition is // ".". string namespace = 9; + // The solution principal: the identity the installation's data is owned by + // and the installation acts as for itself, a stable UUID per organization + // and target, reused by a reinstall. Empty for a module installation. + string principal_id = 10; + // The roles the solution declares, sorted. + repeated string roles = 11; // Status is the installation's lifecycle. enum Status { diff --git a/modules/console/services/api/service.codefly.yaml b/modules/console/services/api/service.codefly.yaml index 8b7968f4..c244af69 100644 --- a/modules/console/services/api/service.codefly.yaml +++ b/modules/console/services/api/service.codefly.yaml @@ -32,7 +32,7 @@ spec: # The console reaches the authority through the interface it depends on, never a # service: the composition resolves which implementation answers. service-dependencies: - - interface: codefly.dev/authority@0.2.0 + - interface: codefly.dev/authority@>=0.2.0 <0.4.0 # The console acts as a workload of its own: the run vouches for it and delivers # its manifest to the authority. module-identity: true From 831beceb4121d06f16c237797dc1e717543e708d Mon Sep 17 00:00:00 2001 From: antoinetoussaint-byte <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:25:51 -0400 Subject: [PATCH 02/12] wip: the local authority as a host over the core (tests in progress) --- .../services/local/code/composition_test.go | 2 +- modules/authority/services/local/code/go.mod | 3 + .../code/pkg/business/issuer/artifact.go | 6 +- .../code/pkg/business/issuer/artifact_test.go | 8 +- .../code/pkg/business/issuer/child_a_test.go | 28 - .../issuer/child_binding_preflight_test.go | 50 - .../pkg/business/issuer/child_delegation.go | 35 - .../issuer/delegation_revocation_test.go | 195 -- .../issuer/installation_chain_test.go | 183 -- .../issuer/installation_roles_test.go | 16 +- .../issuer/issuance_consistency_test.go | 14 +- .../local/code/pkg/business/issuer/issuer.go | 721 +------- .../code/pkg/business/issuer/issuer_test.go | 10 +- .../pkg/business/issuer/owner_refused_test.go | 30 - .../pkg/business/issuer/parent_rows_test.go | 52 - .../pkg/business/issuer/resources_test.go | 10 +- .../code/pkg/business/issuer/scenario_test.go | 227 --- .../code/pkg/business/issuer/scope_limits.go | 80 - .../pkg/business/issuer/solution_roles.go | 90 - .../business/issuer/solution_roles_test.go | 383 ---- .../business/issuer/solution_tools_test.go | 4 +- .../code/pkg/business/state/approvals.go | 6 - .../code/pkg/business/state/composition.go | 6 +- .../pkg/business/state/composition_test.go | 12 +- .../business/state/delegation_fixture_test.go | 6 +- .../local/code/pkg/business/state/delivery.go | 64 +- .../pkg/business/state/installation_access.go | 34 - .../business/state/installation_seal_test.go | 62 - .../code/pkg/business/state/permissions.go | 124 +- .../pkg/business/state/permissions_test.go | 8 +- .../business/state/restricted_method_test.go | 2 +- .../code/pkg/business/state/route_test.go | 8 +- .../code/pkg/business/state/served_test.go | 4 +- .../code/pkg/business/state/solution_roles.go | 48 - .../local/code/pkg/business/state/state.go | 1576 ++++------------- .../code/pkg/business/state/state_test.go | 138 +- .../local/code/pkg/business/testkit/person.go | 98 + .../pkg/business/testkit/solution_tools.go | 6 +- .../code/pkg/business/testkit/testkit.go | 25 +- .../authority/installation_status_test.go | 6 +- .../local/code/plugins/authority/plugin.go | 36 +- .../composition/carrier_lifecycle_test.go | 26 +- .../code/plugins/composition/carrier_test.go | 4 +- .../composition/consumed_roles_test.go | 29 +- .../local/code/plugins/composition/plugin.go | 7 +- .../code/plugins/installation/access_test.go | 3 +- .../local/code/plugins/installation/plugin.go | 106 +- .../registry_test/local_authority_test.go | 14 +- .../solution_principal_target_test.go | 2 +- modules/authority/services/local/code/work.go | 13 +- 50 files changed, 908 insertions(+), 3712 deletions(-) delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/child_binding_preflight_test.go delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/child_delegation.go delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/delegation_revocation_test.go delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/installation_chain_test.go delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/owner_refused_test.go delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/scenario_test.go delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/scope_limits.go delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/solution_roles.go delete mode 100644 modules/authority/services/local/code/pkg/business/issuer/solution_roles_test.go delete mode 100644 modules/authority/services/local/code/pkg/business/state/installation_access.go delete mode 100644 modules/authority/services/local/code/pkg/business/state/installation_seal_test.go delete mode 100644 modules/authority/services/local/code/pkg/business/state/solution_roles.go create mode 100644 modules/authority/services/local/code/pkg/business/testkit/person.go diff --git a/modules/authority/services/local/code/composition_test.go b/modules/authority/services/local/code/composition_test.go index f89ecd9c..3329022f 100644 --- a/modules/authority/services/local/code/composition_test.go +++ b/modules/authority/services/local/code/composition_test.go @@ -58,7 +58,7 @@ func TestCompositionReachesBootThroughCodefly(t *testing.T) { } } k := testkit.New(t) - ledger := state.Manifest{Module: "ledger", Principal: "ledger", Kind: "module", Generation: 1, ResourceKinds: []*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "write")}} + ledger := state.Manifest{Name: "ledger", Principal: "ledger", Kind: "module", Generation: 1, ResourceKinds: []*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "write")}} if _, err := k.State.ApplyManifest(ledger); err != nil { t.Fatal(err) } diff --git a/modules/authority/services/local/code/go.mod b/modules/authority/services/local/code/go.mod index 6fbf3899..c8c16c8f 100644 --- a/modules/authority/services/local/code/go.mod +++ b/modules/authority/services/local/code/go.mod @@ -31,6 +31,7 @@ require ( github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261026024334-2737858746e8 // indirect + github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0 github.com/go-openapi/analysis v1.0.0 // indirect github.com/go-openapi/errors v0.22.9 // indirect github.com/go-openapi/jsonpointer v1.0.1 // indirect @@ -77,3 +78,5 @@ replace github.com/codefly-dev/authority/libraries/delivery/go => ../../../../.. replace github.com/codefly-dev/authority/libraries/resources/go => ../../../../../libraries/resources/go replace github.com/codefly-dev/authority/libraries/hop/go => ../../../../../libraries/hop/go + +replace github.com/codefly-dev/authority/libraries/authority-core/go => ../../../../../libraries/authority-core/go diff --git a/modules/authority/services/local/code/pkg/business/issuer/artifact.go b/modules/authority/services/local/code/pkg/business/issuer/artifact.go index 7b225873..8e9786a5 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/artifact.go +++ b/modules/authority/services/local/code/pkg/business/issuer/artifact.go @@ -72,7 +72,7 @@ func (i *Issuer) artifactCaller(ctx context.Context, parent, credential, install if err != nil { return state.Installation{}, err } - verified, err := i.verifier(decoded.Context().GetAudience()).Verify(ctx, parent) + verified, err := i.svc.Verify(ctx, parent, decoded.Context().GetAudience()) if err != nil { return state.Installation{}, err } @@ -82,7 +82,7 @@ func (i *Issuer) artifactCaller(ctx context.Context, parent, credential, install return state.Installation{}, fmt.Errorf("an artifact request names the module asking: %w", err) } if claims.GetAudience() != actor.Principal { - return state.Installation{}, fmt.Errorf("%w: %s asks about a context addressed to %s", ErrRefused, actor.Module, claims.GetAudience()) + return state.Installation{}, fmt.Errorf("%w: %s asks about a context addressed to %s", ErrRefused, actor.Name, claims.GetAudience()) } inst, ok := i.state.Installation(installation) if !ok || inst.Status != state.Active || inst.Organization != claims.GetTenantId() { @@ -92,7 +92,7 @@ func (i *Issuer) artifactCaller(ctx context.Context, parent, credential, install return state.Installation{}, fmt.Errorf("%w: consent to an artifact is given by a person present, not through a module acting for them", ErrRefused) } if !state.HoldsAction(verified.EffectiveScopes(), policy, action) { - return state.Installation{}, fmt.Errorf("%w: %s does not hold %s:%s at %s", ErrRefused, claims.GetOwnerPrincipalId(), policy, action, actor.Module) + return state.Installation{}, fmt.Errorf("%w: %s does not hold %s:%s at %s", ErrRefused, claims.GetOwnerPrincipalId(), policy, action, actor.Name) } return inst, nil } diff --git a/modules/authority/services/local/code/pkg/business/issuer/artifact_test.go b/modules/authority/services/local/code/pkg/business/issuer/artifact_test.go index 25c0e43f..2b4a7534 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/artifact_test.go +++ b/modules/authority/services/local/code/pkg/business/issuer/artifact_test.go @@ -22,10 +22,10 @@ import ( func composeRobin(t *testing.T, k *testkit.Kit) { t.Helper() for _, m := range []state.Manifest{ - {Module: "robin", Principal: "robin", Kind: "module", Generation: 1, + {Name: "robin", Principal: "robin", Kind: "module", Generation: 1, ResourceKinds: []*basev0.WorkScopeV1{testkit.Scope("robin.intents", "read", "run", "import", "configure", "activate"), testkit.Scope("robin.tasks", "execute", "read")}, Bindings: map[string]state.Binding{}}, - {Module: "assistant", Principal: "assistant", Kind: "module", Generation: 1, + {Name: "assistant", Principal: "assistant", Kind: "module", Generation: 1, ResourceKinds: []*basev0.WorkScopeV1{testkit.Scope("assistant.task", "run")}, Bindings: map[string]state.Binding{"robin": {ID: "robin", Audience: "robin", InvokeScopes: []*basev0.WorkScopeV1{testkit.Scope("robin.intents", "activate", "run")}}}}, @@ -153,7 +153,7 @@ func TestAnInstallationNamesItsNamespace(t *testing.T) { k := testkit.New(t) composeRobin(t, k) inst, ok := k.State.Installation(state.InstallationID("acme", "robin")) - if !ok || inst.Namespace() != "acme.robin" { - t.Fatalf("namespace = %q (%v)", inst.Namespace(), ok) + if !ok || state.Namespace(inst) != "acme.robin" { + t.Fatalf("namespace = %q (%v)", state.Namespace(inst), ok) } } diff --git a/modules/authority/services/local/code/pkg/business/issuer/child_a_test.go b/modules/authority/services/local/code/pkg/business/issuer/child_a_test.go index 8fb66872..fbd92f9d 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/child_a_test.go +++ b/modules/authority/services/local/code/pkg/business/issuer/child_a_test.go @@ -198,31 +198,3 @@ func TestPersonIssueAskPersistsForTheSameKind(t *testing.T) { t.Fatalf("a per-issue narrowing was recovered: %v", effectiveScopes(out.Context)) } } - -func TestInstallationChildProjectsTheTargetRoleAndBinding(t *testing.T) { - k, _ := solutionRoleKit(t, testkit.Scope("documents", "read")) - actor := &authorityv1.WorkloadCredential{Credential: k.Credential(t, "wiki")} - root, err := k.Issuer.Issue(context.Background(), &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Installation{Installation: &authorityv1.InstallationRef{InstallationId: state.InstallationID("acme", "wiki")}}, - Actor: actor, Audience: "wiki", - }) - if err != nil { - t.Fatal(err) - } - req := &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_ParentToken{ParentToken: root.Token}, - Actor: actor, Audience: "documents", OperationBindingId: "documents"} - out, err := k.Issuer.Issue(context.Background(), req) - if err != nil { - t.Fatal(err) - } - if len(effectiveScopes(out.Context)) != 1 || actions(out.Context, "documents") != "read" { - t.Fatalf("installation child retained unrelated role scopes: %v", effectiveScopes(out.Context)) - } - if _, err := k.Issuer.Check(context.Background(), out.Token, "documents", ""); err != nil { - t.Fatal(err) - } - req.Scopes = scopes(testkit.Scope("documents", "write")) - if _, err := k.Issuer.Issue(context.Background(), req); !errors.Is(err, issuer.ErrRefused) { - t.Fatalf("installation role widened by target binding: %v", err) - } -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/child_binding_preflight_test.go b/modules/authority/services/local/code/pkg/business/issuer/child_binding_preflight_test.go deleted file mode 100644 index fe7c4da7..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/child_binding_preflight_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package issuer_test - -import ( - "crypto/ed25519" - "crypto/rand" - "testing" - "time" - - "gateway/pkg/business/issuer" - "gateway/pkg/business/state" - "gateway/pkg/business/testkit" - authorityv1 "gateway/pkg/gen/host/authority/v1" - base "github.com/codefly-dev/core/generated/go/codefly/base/v0" - "github.com/codefly-dev/core/workcontext" - "github.com/google/uuid" -) - -func TestInvalidChildBindingDoesNotSpendSingleUseParent(t *testing.T) { - for _, binding := range []string{"", "undeclared", "records"} { - t.Run(binding, func(t *testing.T) { - k := testkit.New(t) - _, key, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - now := time.Now // the replay store expires entries against wall-clock time - k.Issuer = issuer.New(k.State, testkit.Audience, "one-use", key, now) - authority := &workcontext.Authority{Issuer: testkit.Audience, KeyID: "one-use", Key: key, Revisions: k.State, Seals: k.State, MaxTTL: time.Minute, Now: now} - parent, _, err := authority.Start(t.Context(), workcontext.StartInput{ - TenantID: "acme", OrganizationID: "acme", OwnerPrincipalID: "remi", OwnerPrincipalKind: "human", - TaskID: uuid.NewString(), Audience: "org-directory", AuthorityScopes: []*base.WorkScopeV1{testkit.Scope("directory.entry", "read")}, - InstallationID: state.InstallationID("acme", "org-directory"), ReplayPolicy: workcontext.ReplaySingleUse, TTL: time.Minute, - }) - if err != nil { - t.Fatal(err) - } - _, err = k.Issuer.Issue(t.Context(), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_ParentToken{ParentToken: parent}, - Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "org-directory")}, Audience: "documents", OperationBindingId: binding}) - if err == nil { - t.Fatal("invalid binding was accepted") - } - if _, err := k.Issuer.Check(t.Context(), parent, "org-directory", ""); err != nil { - t.Fatalf("refused hop spent its parent: %v", err) - } - if _, err := k.Issuer.Check(t.Context(), parent, "org-directory", ""); err == nil { - t.Fatal("fixture is not single use") - } - }) - } -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/child_delegation.go b/modules/authority/services/local/code/pkg/business/issuer/child_delegation.go deleted file mode 100644 index bcfd3190..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/child_delegation.go +++ /dev/null @@ -1,35 +0,0 @@ -package issuer - -import ( - "errors" - "fmt" - "gateway/pkg/business/state" - - base "github.com/codefly-dev/core/generated/go/codefly/base/v0" -) - -// delegationCeiling reads durable restrictions from verified provenance. -// Ordinary child hops name parent sessions, not stored Delegate records. -// Revoked records are retained; the final parent recheck fences concurrent -// revocation, so reconstruction cannot refresh an invalidated grant. -func (i *Issuer) delegationCeiling(parent *base.WorkContextV1, ceiling []*base.WorkScopeV1) ([]*base.WorkScopeV1, error) { - for _, hop := range parent.GetActorChain() { - d, err := i.state.DelegationByID(hop.GetDelegationId()) - if errors.Is(err, state.ErrUnknown) { - continue - } - if err != nil { - return nil, err - } - module, err := i.state.ModuleByPrincipal(hop.GetPrincipalId()) - if err != nil { - return nil, err - } - if d.Revoked || d.Grantor != parent.GetOwnerPrincipalId() || - d.Organization != parent.GetOrganizationId() || d.Module != module.Module { - return nil, fmt.Errorf("%w: the explicit delegation does not authorize this actor chain", ErrRefused) - } - ceiling = state.Intersect(ceiling, d.WorkScopes()) - } - return ceiling, nil -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/delegation_revocation_test.go b/modules/authority/services/local/code/pkg/business/issuer/delegation_revocation_test.go deleted file mode 100644 index f4d2edbd..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/delegation_revocation_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package issuer_test - -import ( - "context" - "crypto/ed25519" - "crypto/rand" - "errors" - "fmt" - "gateway/pkg/business/issuer" - "testing" - "time" - - "gateway/pkg/business/testkit" - authorityv1 "gateway/pkg/gen/host/authority/v1" - "github.com/codefly-dev/core/workcontext" -) - -func TestDelegationRevocationInvalidatesExistingUsesAndPreservesOtherTenants(t *testing.T) { - k := testkit.New(t) - declareDelegationChain(t, k) - ctx := context.Background() - grantor := person(t, k, "fixture-remi") - d, err := k.Issuer.Delegate(grantor, "org-directory", - append(testkit.OrgDirectory(1).Bindings["documents"].InvokeScopes, testkit.Documents(1).Bindings["model"].InvokeScopes...), "bounded work") - if err != nil { - t.Fatal(err) - } - delegated, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Delegation{Delegation: &authorityv1.DelegationRef{DelegationId: d.ID}}, - Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "org-directory")}, - Audience: "documents", OperationBindingId: "documents", - }) - if err != nil { - t.Fatal(err) - } - other, err := k.Issuer.Issue(person(t, k, "fixture-zoe"), &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "documents", - }) - if err != nil { - t.Fatal(err) - } - descendant, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_ParentToken{ParentToken: delegated.GetToken()}, - Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "documents")}, - Audience: "model", OperationBindingId: "model", - }) - if err != nil { - t.Fatal(err) - } - uses := map[string]func() error{ - "existing descendant": func() error { _, err := k.Issuer.Check(ctx, descendant.GetToken(), "model", ""); return err }, - "check": func() error { _, err := k.Issuer.Check(ctx, delegated.GetToken(), "documents", ""); return err }, - "resources": func() error { - _, err := k.Issuer.ListResources(ctx, delegated.GetToken(), "documents", "documents", "read") - return err - }, - "child": func() error { - _, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_ParentToken{ParentToken: delegated.GetToken()}, - Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "documents")}, - Audience: "model", OperationBindingId: "model", - }) - return err - }, - } - for name, use := range uses { - if err := use(); err != nil { - t.Fatalf("%s before revocation: %v", name, err) - } - } - if _, err := k.Issuer.RevokeDelegation(grantor, d.ID); err != nil { - t.Fatal(err) - } - for name, use := range uses { - if err := use(); !errors.Is(err, workcontext.ErrRevoked) { - t.Errorf("%s after revocation: %v", name, err) - } - } - if _, err := k.Issuer.Check(ctx, other.GetToken(), "documents", ""); err != nil { - t.Fatalf("another tenant lost authority: %v", err) - } - fresh, err := k.Issuer.Issue(grantor, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "documents", - }) - if err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.RevokeDelegation(grantor, d.ID); err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.Check(ctx, fresh.GetToken(), "documents", ""); err != nil { - t.Fatalf("repeating an already-complete revocation invalidated fresh authority: %v", err) - } -} - -// Exercise revocation at every clock boundary of issuance. The clock remains -// fixed; only the interleaving changes. A refusal or a token rejected by the -// ordinary verifier is safe, but refreshed authority after revocation is not. -func TestRevocationDuringIssuanceCannotRefreshAuthority(t *testing.T) { - for _, child := range []bool{false, true} { - name := "delegation" - if child { - name = "parent" - } - t.Run(name, func(t *testing.T) { - run := func(t *testing.T, revokeAt int) int { - k := testkit.New(t) - declareDelegationChain(t, k) - _, key, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - ticks, armed := 0, false - var delegationID string - grantor := person(t, k, "fixture-remi") - k.Issuer = issuer.New(k.State, testkit.Audience, "race-test", key, func() time.Time { - if armed { - ticks++ - if ticks == revokeAt { - if _, err := k.State.RevokeDelegation("remi", delegationID); err != nil { - t.Fatal(err) - } - } - } - return k.Now - }) - d, err := k.Issuer.Delegate(grantor, "org-directory", append(testkit.OrgDirectory(1).Bindings["documents"].InvokeScopes, testkit.Documents(1).Bindings["model"].InvokeScopes...), "race test") - if err != nil { - t.Fatal(err) - } - delegationID = d.ID - req := &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Delegation{Delegation: &authorityv1.DelegationRef{DelegationId: d.ID}}, - Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "org-directory")}, - Audience: "documents", OperationBindingId: "documents", - } - if child { - parent, err := k.Issuer.Issue(context.Background(), req) - if err != nil { - t.Fatal(err) - } - req = &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_ParentToken{ParentToken: parent.GetToken()}, - Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "documents")}, - Audience: "model", OperationBindingId: "model", - } - } - armed = true - out, issueErr := k.Issuer.Issue(context.Background(), req) - armed = false - if revokeAt == 0 { - if issueErr != nil { - t.Fatal(issueErr) - } - } else { - if ticks < revokeAt { - t.Fatalf("revocation point %d not reached (%d)", revokeAt, ticks) - } - if issueErr == nil { - if _, err := k.Issuer.Check(context.Background(), out.GetToken(), req.Audience, ""); !errors.Is(err, workcontext.ErrRevoked) { - t.Fatalf("issuance refreshed revoked authority at clock boundary %d: %v", revokeAt, err) - } - } - } - return ticks - } - count := run(t, 0) - for tick := 1; tick <= count; tick++ { - t.Run(fmt.Sprintf("clock-%d", tick), func(t *testing.T) { run(t, tick) }) - } - }) - } -} - -func TestFailedRevocationDoesNotInvalidateExistingAuthority(t *testing.T) { - k := testkit.New(t) - grantor := person(t, k, "fixture-remi") - d, err := k.Issuer.Delegate(grantor, "org-directory", testkit.OrgDirectory(1).Bindings["documents"].InvokeScopes, "failure isolation") - if err != nil { - t.Fatal(err) - } - before, err := k.State.AuthorizationRevision(context.Background(), d.Organization) - if err != nil { - t.Fatal(err) - } - for _, attempt := range []struct{ person, id string }{{"maya", d.ID}, {"remi", "unknown-delegation"}} { - if _, err := k.State.RevokeDelegation(attempt.person, attempt.id); err == nil { - t.Fatal("invalid revocation succeeded") - } - after, err := k.State.AuthorizationRevision(context.Background(), d.Organization) - if err != nil || after != before { - t.Fatalf("failed revocation changed revision: %d -> %d (%v)", before, after, err) - } - } -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/installation_chain_test.go b/modules/authority/services/local/code/pkg/business/issuer/installation_chain_test.go deleted file mode 100644 index 92df88ef..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/installation_chain_test.go +++ /dev/null @@ -1,183 +0,0 @@ -package issuer_test - -import ( - "context" - "crypto/ed25519" - "crypto/rand" - "fmt" - "testing" - "time" - - "gateway/pkg/business/issuer" - "gateway/pkg/business/state" - "gateway/pkg/business/testkit" - authorityv1 "gateway/pkg/gen/host/authority/v1" -) - -func installationChainParent(t *testing.T, k *testkit.Kit) *authorityv1.IssueResponse { - t.Helper() - ctx := context.Background() - credential := &authorityv1.WorkloadCredential{Credential: k.Credential(t, "wiki")} - root, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Installation{Installation: &authorityv1.InstallationRef{InstallationId: state.InstallationID("acme", "wiki")}}, - Actor: credential, Audience: "wiki", - }) - if err != nil { - t.Fatal(err) - } - parent, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_ParentToken{ParentToken: root.Token}, Actor: credential, - Audience: "org-directory", OperationBindingId: "directory", - }) - if err != nil { - t.Fatal(err) - } - return parent -} - -func installationChainRequest(t *testing.T, k *testkit.Kit, parent *authorityv1.IssueResponse) *authorityv1.IssueRequest { - t.Helper() - return &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_ParentToken{ParentToken: parent.Token}, - Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "org-directory")}, - Audience: "documents", OperationBindingId: "documents", - } -} - -func TestInstallationChainRetainsOwnerRoleResourcesAndProvenance(t *testing.T) { - held := testkit.Scope("documents", "read") - held.ResourceIds = []string{"one"} - k, _ := solutionRoleKit(t, held) - parent := installationChainParent(t, k) - req := installationChainRequest(t, k, parent) - ctx := context.Background() - out, err := k.Issuer.Issue(ctx, req) - if err != nil { - t.Fatal(err) - } - wc, before := out.Context, parent.Context - if wc.OwnerPrincipalId != state.InstallationID("acme", "wiki")+"/agent" || - wc.GetOwnerPrincipalKind() != "agent" || wc.GetOwnerAgentId() != "wiki" || - wc.TenantId != before.TenantId || wc.TaskId != before.TaskId || - wc.GetParentSessionId() != before.SessionId || wc.ExpiresAtUnix > before.ExpiresAtUnix || - len(wc.ActorChain) != 2 || wc.ActorChain[0].PrincipalId != "wiki" || wc.ActorChain[1].PrincipalId != "org-directory" || - wc.Seal.InstallationId != state.InstallationID("acme", "org-directory") || - wc.OperationBinding.BindingId != "acme/org-directory/documents" { - t.Fatalf("installation provenance changed: %v", wc) - } - if len(effectiveScopes(wc)) != 1 || actions(wc, "documents") != "read" || - len(effectiveScopes(wc)[0].ResourceIds) != 1 || effectiveScopes(wc)[0].ResourceIds[0] != "one" { - t.Fatalf("role projection changed: %v", effectiveScopes(wc)) - } - if _, err := k.Issuer.Check(ctx, out.Token, "documents", ""); err != nil { - t.Fatal(err) - } - grants, err := k.Issuer.ListResources(ctx, out.Token, "documents", "documents", "read") - if err != nil || !covered(t, grants).Matches("one") || covered(t, grants).Matches("two") || covered(t, grants).All() { - t.Fatalf("resource ceiling lost: %v %v", grants, err) - } - for _, ask := range []*authorityv1.WorkScopeV1{ - {ResourceKind: "documents", Actions: []string{"write"}, ResourceIds: []string{"one"}}, - {ResourceKind: "documents", Actions: []string{"read"}}, - {ResourceKind: "documents", Actions: []string{"read"}, ResourceIds: []string{"two"}}, - } { - req.Scopes = []*authorityv1.WorkScopeV1{ask} - if _, err := k.Issuer.Issue(ctx, req); err == nil { - t.Fatalf("role restriction widened: %v", ask) - } - } - req.Scopes = nil - req.Actor.Credential = k.Credential(t, "documents") - if _, err := k.Issuer.Issue(ctx, req); err == nil { - t.Fatal("wrong actor accepted") - } -} - -func TestInstallationChainRevocation(t *testing.T) { - for _, change := range []string{"source uninstall", "actor uninstall", "role narrowing", "binding narrowing"} { - t.Run(change, func(t *testing.T) { - k, solution := solutionRoleKit(t, testkit.Scope("documents", "read")) - parent := installationChainParent(t, k) - req := installationChainRequest(t, k, parent) - ctx := context.Background() - out, err := k.Issuer.Issue(ctx, req) - if err != nil { - t.Fatal(err) - } - switch change { - case "source uninstall": - err = k.State.Revoke(state.InstallationID("acme", "wiki")) - case "actor uninstall": - err = k.State.Revoke(state.InstallationID("acme", "org-directory")) - case "role narrowing": - solution.Generation++ - solution.Roles["viewer"] = append(testkit.Wiki(1).Roles["viewer"], testkit.Scope("directory.entry", "read")) - _, err = k.State.ApplyManifest(solution) - case "binding narrowing": - module := testkit.OrgDirectory(2) - delete(module.Bindings, "documents") - _, err = k.State.ApplyManifest(module) - } - if err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.Check(ctx, out.Token, "documents", ""); err == nil { - t.Fatal("old child survived revocation") - } - if _, err := k.Issuer.Issue(ctx, req); err == nil { - t.Fatal("parent refreshed revoked authority") - } - if change == "role narrowing" || change == "binding narrowing" { - fresh := installationChainRequest(t, k, installationChainParent(t, k)) - if _, err := k.Issuer.Issue(ctx, fresh); err == nil { - t.Fatal("fresh chain recovered removed authority") - } - } - }) - } -} - -func TestInstallationUninstallDuringSecondHopCannotRefreshAuthority(t *testing.T) { - run := func(t *testing.T, revokeAt int) int { - k, _ := solutionRoleKit(t, testkit.Scope("documents", "read")) - _, key, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - ticks, armed := 0, false - k.Issuer = issuer.New(k.State, testkit.Audience, "installation-race", key, func() time.Time { - if armed { - ticks++ - if ticks == revokeAt { - if err := k.State.Revoke(state.InstallationID("acme", "wiki")); err != nil { - t.Fatal(err) - } - } - } - return k.Now - }) - req := installationChainRequest(t, k, installationChainParent(t, k)) - armed = true - out, issueErr := k.Issuer.Issue(context.Background(), req) - armed = false - if revokeAt == 0 { - if issueErr != nil { - t.Fatal(issueErr) - } - } else { - if ticks < revokeAt { - t.Fatalf("revocation point %d not reached (%d)", revokeAt, ticks) - } - if issueErr == nil { - if _, err := k.Issuer.Check(context.Background(), out.Token, "documents", ""); err == nil { - t.Fatalf("issuance refreshed revoked installation at clock %d", revokeAt) - } - } - } - return ticks - } - count := run(t, 0) - for tick := 1; tick <= count; tick++ { - t.Run(fmt.Sprintf("clock-%d", tick), func(t *testing.T) { run(t, tick) }) - } -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/installation_roles_test.go b/modules/authority/services/local/code/pkg/business/issuer/installation_roles_test.go index 908ac342..7ce4a088 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/installation_roles_test.go +++ b/modules/authority/services/local/code/pkg/business/issuer/installation_roles_test.go @@ -15,7 +15,7 @@ import ( func installationManifest(name, role string, generation uint64, actions ...string) state.Manifest { kind := name + ".item" - return state.Manifest{Module: name, Principal: name, Kind: "solution", Generation: generation, + return state.Manifest{Name: name, Principal: name, Kind: "solution", Generation: generation, ResourceKinds: []*basev0.WorkScopeV1{testkit.Scope(kind, "read", "write")}, Roles: map[string][]*basev0.WorkScopeV1{state.InstallerRole: {testkit.Scope(kind, "read", "write")}, role: {testkit.Scope(kind, actions...)}}, Bindings: map[string]state.Binding{"self": {ID: "self", Audience: name, InvokeScopes: []*basev0.WorkScopeV1{testkit.Scope(kind, "read", "write")}}}} @@ -68,7 +68,7 @@ func TestSolutionRoleNarrowingRevokesExistingRootsAndChildren(t *testing.T) { if _, err := k.State.ApplyManifest(manifest); err != nil { t.Fatal(err) } - inst, err := k.State.Install(ctx, "acme", manifest.Module, "task_operator", "alex") + inst, err := k.State.Install(ctx, "acme", manifest.Name, "task_operator", "alex") if err != nil { t.Fatal(err) } @@ -77,7 +77,7 @@ func TestSolutionRoleNarrowingRevokesExistingRootsAndChildren(t *testing.T) { if err != nil { t.Fatal(err) } - child, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_ParentToken{ParentToken: root.GetToken()}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, manifest.Module)}, Audience: manifest.Principal, OperationBindingId: "self", Scopes: request.Scopes}) + child, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_ParentToken{ParentToken: root.GetToken()}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, manifest.Name)}, Audience: manifest.Principal, OperationBindingId: "self", Scopes: request.Scopes}) if err != nil { t.Fatal(err) } @@ -123,7 +123,7 @@ func TestInstallerNarrowingRevokesTheOwnerButRoleExpansionKeepsExistingAuthority if _, err := k.State.ApplyManifest(m); err != nil { t.Fatal(err) } - inst, err := k.State.Install(ctx, "acme", m.Module, "viewer", "alex") + inst, err := k.State.Install(ctx, "acme", m.Name, "viewer", "alex") if err != nil { t.Fatal(err) } @@ -131,12 +131,12 @@ func TestInstallerNarrowingRevokesTheOwnerButRoleExpansionKeepsExistingAuthority if err != nil { t.Fatal(err) } - owner, err := k.Issuer.Issue(person(t, k, "fixture-alex"), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: m.Principal, Scopes: scopes(testkit.Scope(m.Module+".item", "write"))}) + owner, err := k.Issuer.Issue(person(t, k, "fixture-alex"), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: m.Principal, Scopes: scopes(testkit.Scope(m.Name+".item", "write"))}) if err != nil { t.Fatal(err) } // Expanding the installed viewer role leaves already-issued narrow grants valid. - next := installationManifest(m.Module, "viewer", 2, "read", "write") + next := installationManifest(m.Name, "viewer", 2, "read", "write") if _, err := k.State.ApplyManifest(next); err != nil { t.Fatal(err) } @@ -144,8 +144,8 @@ func TestInstallerNarrowingRevokesTheOwnerButRoleExpansionKeepsExistingAuthority t.Fatalf("expansion invalidated old scope: %v", err) } // The owner's installer role is independent of that selected viewer role. - next = installationManifest(m.Module, "viewer", 3, "read", "write") - next.Roles[state.InstallerRole] = []*basev0.WorkScopeV1{testkit.Scope(m.Module+".item", "read")} + next = installationManifest(m.Name, "viewer", 3, "read", "write") + next.Roles[state.InstallerRole] = []*basev0.WorkScopeV1{testkit.Scope(m.Name+".item", "read")} if _, err := k.State.ApplyManifest(next); err != nil { t.Fatal(err) } diff --git a/modules/authority/services/local/code/pkg/business/issuer/issuance_consistency_test.go b/modules/authority/services/local/code/pkg/business/issuer/issuance_consistency_test.go index a6bc6909..3d5762ae 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/issuance_consistency_test.go +++ b/modules/authority/services/local/code/pkg/business/issuer/issuance_consistency_test.go @@ -66,30 +66,30 @@ func TestRoleNarrowingBetweenResolutionAndMint(t *testing.T) { if _, err := k.State.ApplyManifest(old); err != nil { t.Fatal(err) } - inst, err := k.State.Install(ctx, "acme", old.Module, "task_operator", "alex") + inst, err := k.State.Install(ctx, "acme", old.Name, "task_operator", "alex") if err != nil { t.Fatal(err) } - req := &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Installation{Installation: &authorityv1.InstallationRef{InstallationId: inst.ID}}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, inst.Target)}, Audience: old.Principal, Scopes: scopes(testkit.Scope(old.Module+".item", "write"))} - next := installationManifest(old.Module, "task_operator", 2, "read") + req := &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Installation{Installation: &authorityv1.InstallationRef{InstallationId: inst.ID}}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, inst.Target)}, Audience: old.Principal, Scopes: scopes(testkit.Scope(old.Name+".item", "write"))} + next := installationManifest(old.Name, "task_operator", 2, "read") if owner == "person" { ctx = person(t, k, "fixture-alex") req.As = &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}} } if owner == "person" || owner == "delegation" { - next.Roles[state.InstallerRole] = []*basev0.WorkScopeV1{testkit.Scope(old.Module+".item", "read")} + next.Roles[state.InstallerRole] = []*basev0.WorkScopeV1{testkit.Scope(old.Name+".item", "read")} } if owner == "installation_removed" { delete(next.Roles, "task_operator") } if owner == "delegation" { ctx = person(t, k, "fixture-alex") - d, err := k.Issuer.Delegate(ctx, old.Module, []*basev0.WorkScopeV1{testkit.Scope(old.Module+".item", "write")}, "consistency regression") + d, err := k.Issuer.Delegate(ctx, old.Name, []*basev0.WorkScopeV1{testkit.Scope(old.Name+".item", "write")}, "consistency regression") if err != nil { t.Fatal(err) } req.As = &authorityv1.IssueRequest_Delegation{Delegation: &authorityv1.DelegationRef{DelegationId: d.ID}} - req.Actor = &authorityv1.WorkloadCredential{Credential: k.Credential(t, old.Module)} + req.Actor = &authorityv1.WorkloadCredential{Credential: k.Credential(t, old.Name)} req.OperationBindingId = "self" } if owner == "child" { @@ -98,7 +98,7 @@ func TestRoleNarrowingBetweenResolutionAndMint(t *testing.T) { t.Fatal(err) } req.As = &authorityv1.IssueRequest_ParentToken{ParentToken: parent.Token} - req.Actor = &authorityv1.WorkloadCredential{Credential: k.Credential(t, old.Module)} + req.Actor = &authorityv1.WorkloadCredential{Credential: k.Credential(t, old.Name)} req.OperationBindingId = "self" } // Prove this owner can issue the exact requested scope before scheduling a change. diff --git a/modules/authority/services/local/code/pkg/business/issuer/issuer.go b/modules/authority/services/local/code/pkg/business/issuer/issuer.go index 9d14ae1b..b7984f4e 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/issuer.go +++ b/modules/authority/services/local/code/pkg/business/issuer/issuer.go @@ -1,8 +1,9 @@ -// Package issuer is the authority interface on top of core's Work Context -// library: Issue for every owner shape, Check for every callee, Keys for every -// verifier. It mints with core's Authority and verifies with core's Verifier, -// so the laptop signs and refuses exactly as a cell does; what it adds is only -// the resolution of an owner shape into core's inputs against the state. +// Package issuer is the authority interface over the generic authority core: +// Issue for every owner shape, Check for every callee, Keys for every +// verifier. Every decision and every signature is the core's; what this +// package adds is the wire: a session bearer resolved to the person, a +// workload credential attested to its module, and a method's declared policy +// applied on Check. package issuer import ( @@ -10,14 +11,11 @@ import ( "crypto/ed25519" "errors" "fmt" - resources "github.com/codefly-dev/authority/libraries/resources/go" - "slices" - "strings" "time" + "github.com/codefly-dev/authority/libraries/authority-core/go/authority" basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" "github.com/codefly-dev/core/workcontext" - "github.com/google/uuid" "google.golang.org/protobuf/proto" "gateway/pkg/business/state" @@ -41,101 +39,43 @@ func sessionOf(ctx context.Context) (string, bool) { return id, ok && id != "" } -// ErrRefused is the umbrella for a refusal the caller can do nothing about. +// ErrRefused is the umbrella for a refusal this wire layer makes itself. var ErrRefused = errors.New("issuer: refused") -// Issuer signs and verifies. +// Issuer is the wire over one host and its core. type Issuer struct { - state *state.State - authority *workcontext.Authority - replay workcontext.ReplayStore - public ed25519.PublicKey + state *state.State + svc *authority.Service + issuer string + keyID string + public ed25519.PublicKey } -// New builds an issuer for one host identity and one signing key. -func New(st *state.State, issuer, keyID string, key ed25519.PrivateKey, now func() time.Time) *Issuer { - return &Issuer{ - state: st, - public: key.Public().(ed25519.PublicKey), - replay: workcontext.NewMemoryReplayStore(), - authority: &workcontext.Authority{ - Issuer: issuer, KeyID: keyID, Key: key, Revisions: st, Seals: st, MaxTTL: MaxTTL, Now: now, - }, - } +// New builds the issuer for a host whose core signs as issuer with keyID, its +// public half published by Keys. +func New(st *state.State, issuer, keyID string, public ed25519.PublicKey) *Issuer { + return &Issuer{state: st, svc: st.Service(), issuer: issuer, keyID: keyID, public: public} +} + +// Config is the core configuration for one signing key: what state.New takes. +func Config(issuer, keyID string, key ed25519.PrivateKey, now func() time.Time) authority.Config { + return authority.Config{Issuer: issuer, KeyID: keyID, Signer: key, Clock: now, MaxTTL: MaxTTL} } // Keys is what a verifier pins. func (i *Issuer) Keys() *authorityv1.KeysResponse { - return &authorityv1.KeysResponse{Issuer: i.authority.Issuer, Keys: []*authorityv1.PublicKey{{ - KeyId: i.authority.KeyID, Algorithm: workcontext.Algorithm, PublicKey: []byte(i.public), + return &authorityv1.KeysResponse{Issuer: i.issuer, Keys: []*authorityv1.PublicKey{{ + KeyId: i.keyID, Algorithm: workcontext.Algorithm, PublicKey: []byte(i.public), }}} } -func (i *Issuer) verifier(audience string) *workcontext.Verifier { - return &workcontext.Verifier{ - Issuer: i.authority.Issuer, Audience: audience, - Keys: map[string]ed25519.PublicKey{i.authority.KeyID: i.public}, - Revisions: i.state, Replay: i.replay, Seals: i.state, Grants: i.state, Now: i.authority.Now, - } -} - // ListResources answers which resources of kind a checked capability may act -// on with action, decided now rather than carried in the capability. The -// capability must hold the action on the kind. A person's answer is what her -// roles grant; a module's or an installation's own work is bounded by the -// capability alone. When the capability's chain names resource ids, only those -// the owner's grants cover remain. -func (i *Issuer) ListResources(ctx context.Context, token, audience, kind, action string) ([]resources.Grant, error) { +// on with action, decided now by the core. +func (i *Issuer) ListResources(ctx context.Context, token, audience, kind, action string) ([]authority.Grant, error) { if audience == "" || kind == "" || action == "" { return nil, fmt.Errorf("%w: a resource question names the audience, a kind and an action", ErrRefused) } - verified, err := i.verifier(audience).Verify(ctx, token) - if err != nil { - return nil, err - } - held, whole := false, false - var ids []string - for _, sc := range verified.EffectiveScopes() { - if sc.GetResourceKind() != kind || (!slices.Contains(sc.GetActions(), action) && !slices.Contains(sc.GetActions(), state.AnyAction)) { - continue - } - held = true - if len(sc.GetResourceIds()) == 0 { - whole = true - } - ids = append(ids, sc.GetResourceIds()...) - } - if !held { - return nil, fmt.Errorf("%w: the capability holds no %s:%s", ErrRefused, kind, action) - } - wc := verified.Context() - grants := []resources.Grant{{All: true}} - if wc.GetOwnerPrincipalKind() == "human" { - ceiling, bounded, err := i.solutionCeiling(wc.GetOwnerPrincipalId(), solutionPrincipals(wc, wc.GetAudience())) - if err != nil { - return nil, err - } - if bounded { - return roleResources(state.Intersect(verified.EffectiveScopes(), ceiling), kind, action), nil - } - if grants, err = i.state.ResourceGrants(wc.GetOwnerPrincipalId(), audience, kind, action); err != nil { - return nil, err - } - } - if whole { - return grants, nil - } - covered, err := resources.Compile(grants) - if err != nil { - return nil, err - } - narrowed := []resources.Grant{} - for _, id := range ids { - if covered.Matches(id) { - narrowed = append(narrowed, resources.Grant{ID: id}) - } - } - return narrowed, nil + return i.svc.ListResources(ctx, token, audience, kind, action) } // Check verifies a capability for the audience the caller is and, when method @@ -147,7 +87,6 @@ func (i *Issuer) Check(ctx context.Context, token, audience, method string) (*au // Advise is Check with the method's policy decided but not enforced: the // capability is verified and refused when unsound; what the policy would have // refused is answered in CheckResponse.Refusal beside the verified capability. -// A hop running permissive asks this way and logs the refusal it did not make. func (i *Issuer) Advise(ctx context.Context, token, audience, method string) (*authorityv1.CheckResponse, error) { return i.check(ctx, token, audience, method, true) } @@ -156,7 +95,7 @@ func (i *Issuer) check(ctx context.Context, token, audience, method string, advi if audience == "" { return nil, fmt.Errorf("%w: a check names the audience the caller is", ErrRefused) } - verified, err := i.verifier(audience).Verify(ctx, token) + verified, revision, err := i.svc.Check(ctx, token, audience) if err != nil { return nil, err } @@ -173,517 +112,111 @@ func (i *Issuer) check(ctx context.Context, token, audience, method string, advi if err != nil { return nil, err } - revision, err := i.state.AuthorizationRevision(ctx, verified.Context().GetTenantId()) - if err != nil { - return nil, err - } return &authorityv1.CheckResponse{Context: wc, AuthorizationRevision: revision, Refusal: refusal}, nil } -// Issue signs a capability for one owner shape. +// Issue signs a capability for one owner shape, through the core. func (i *Issuer) Issue(ctx context.Context, req *authorityv1.IssueRequest) (*authorityv1.IssueResponse, error) { - audienceName := req.GetAudience() - if audienceName == "" && req.GetOperationBindingId() != "" { - // A caller that names the binding it exercises need not know where the - // binding points: the audience is the binding's. Resolved from the - // module acting, a workload by its credential or a hop's actor. - derived, err := i.audienceOfBinding(ctx, req) - if err != nil { - return nil, err - } - audienceName = derived - req.Audience = audienceName - } - if req.GetAudience() == "" { - return nil, fmt.Errorf("%w: an issue names its audience, or the binding whose audience it is", ErrRefused) - } - audience, err := i.state.ModuleByPrincipal(req.GetAudience()) - if err != nil { - return nil, err - } - asked := FromWire(req.GetScopes()) - if err := refuseOwners(asked); err != nil { - return nil, err - } - ttl := time.Duration(req.GetTtlSeconds()) * time.Second - if ttl <= 0 || ttl > MaxTTL { - ttl = MaxTTL - } - var token string - var wc *basev0.WorkContextV1 + r := authority.IssueRequest{Audience: req.GetAudience(), Binding: req.GetOperationBindingId(), Operation: req.GetOperation(), + Organization: req.GetOrganizationId(), Scopes: FromWire(req.GetScopes()), TTL: time.Duration(req.GetTtlSeconds()) * time.Second} + var out authority.Issued + var err error switch as := req.GetAs().(type) { case *authorityv1.IssueRequest_Person: sessionID, ok := sessionOf(ctx) if !ok { return nil, fmt.Errorf("%w: a person-owned issue is made by a signed-in person; the call carries no session", ErrRefused) } - token, wc, err = i.forPerson(ctx, sessionID, audience, req.GetOperationBindingId(), asked, ttl) + person, perr := i.state.Caller(sessionID) + if perr != nil { + return nil, perr + } + out, err = i.svc.IssueForPerson(ctx, person, r) case *authorityv1.IssueRequest_Workload: - token, wc, err = i.forWorkload(ctx, as.Workload.GetCredential(), req.GetOrganizationId(), req.GetOperationBindingId(), req.GetOperation(), audience, asked, ttl) + w, werr := i.workload(ctx, as.Workload.GetCredential(), "a workload issue names the module asking") + if werr != nil { + return nil, werr + } + out, err = i.svc.IssueForWorkload(ctx, w, r) case *authorityv1.IssueRequest_Installation: - token, wc, err = i.forInstallation(ctx, as.Installation.GetInstallationId(), req.GetOrganizationId(), req.GetActor().GetCredential(), audience, asked, ttl) + w, werr := i.workload(ctx, req.GetActor().GetCredential(), "an installation acts through its solution's workload") + if werr != nil { + return nil, werr + } + out, err = i.svc.IssueForInstallation(ctx, w, as.Installation.GetInstallationId(), r) case *authorityv1.IssueRequest_ParentToken: - token, wc, err = i.child(ctx, as.ParentToken, req.GetActor().GetCredential(), req.GetOperationBindingId(), req.GetOperation(), audience, asked, ttl) + w, werr := i.workload(ctx, req.GetActor().GetCredential(), "a child hop names the module acting") + if werr != nil { + return nil, werr + } + out, err = i.svc.IssueChild(ctx, as.ParentToken, w, r) case *authorityv1.IssueRequest_Delegation: - token, wc, err = i.forDelegation(ctx, as.Delegation.GetDelegationId(), req.GetActor().GetCredential(), req.GetOperationBindingId(), req.GetOperation(), audience, asked, ttl) + w, werr := i.workload(ctx, req.GetActor().GetCredential(), "a delegated issue names the module acting") + if werr != nil { + return nil, werr + } + out, err = i.svc.IssueForDelegation(ctx, as.Delegation.GetDelegationId(), w, r) default: return nil, fmt.Errorf("%w: an issue names whom it is for", ErrRefused) } if err != nil { return nil, err } - wire, err := ToWire(wc) + wire, err := ToWire(out.Context) if err != nil { return nil, err } - return &authorityv1.IssueResponse{Token: token, ExpiresAtUnix: wc.GetExpiresAtUnix(), Context: wire}, nil + return &authorityv1.IssueResponse{Token: out.Token, ExpiresAtUnix: out.Context.GetExpiresAtUnix(), Context: wire}, nil } -// forPerson: owner = actor = the person; scopes = asked ∩ what their roles hold -// at the audience; sealed to the audience module's installation at their organisation. -func (i *Issuer) forPerson(ctx context.Context, sessionID string, audience state.Manifest, bindingID string, asked []*basev0.WorkScopeV1, ttl time.Duration) (string, *basev0.WorkContextV1, error) { - sess, err := i.state.Session(sessionID) - if err != nil { - return "", nil, err - } - revision, err := i.state.AuthorizationRevision(ctx, sess.Organization) - if err != nil { - return "", nil, err - } - // Issue's audience lookup precedes this revision snapshot. Resolve any - // binding again below it so a concurrent delivery cannot supply old scopes. - audience, err = i.state.ModuleByPrincipal(audience.Principal) - if err != nil { - return "", nil, err - } - if err := i.state.RequireInstallation(sess.Organization, audience.Module, audience.Principal); err != nil { - return "", nil, err - } - held, err := i.state.PersonScopes(sess.Person, audience.Principal) +// workload attests a credential to the composed module it proves. +func (i *Issuer) workload(ctx context.Context, credential, why string) (authority.Workload, error) { + m, err := i.state.VerifyWorkload(ctx, credential) if err != nil { - return "", nil, err + return authority.Workload{}, fmt.Errorf("%s: %w", why, err) } - if bindingID != "" { - // The person's token names a binding of the audience module, so the - // module's hop through that binding carries exactly what the person - // holds at the binding's audience, and nothing is typed per organisation. - binding, declared := audience.Bindings[bindingID] - if !declared { - return "", nil, fmt.Errorf("%w: operation binding %q is not one %s declares", state.ErrUnknown, bindingID, audience.Module) - } - target, err := i.state.ModuleByPrincipal(binding.Audience) - if err != nil { - return "", nil, err - } - there, _, _, err := i.personThroughSolutions(sess.Person, target, []string{audience.Principal}) - if err != nil { - return "", nil, err - } - through := state.Intersect(there, binding.InvokeScopes) - if len(through) == 0 { - return "", nil, fmt.Errorf("%w: %s holds nothing under operation binding %q", ErrRefused, sess.Person, bindingID) - } - held = state.Normalize(append(held, through...)) - } - scopes, err := grant(asked, held) - if err != nil { - return "", nil, err - } - // Attribution is a set on the wire; never sort the session's shared slice. - teams := slices.Clone(sess.Teams) - slices.Sort(teams) - teams = slices.Compact(teams) - return i.startAtRevision(ctx, revision, workcontext.StartInput{ - TenantID: sess.Organization, OrganizationID: sess.Organization, - OwnerPrincipalID: sess.Person, OwnerPrincipalKind: "human", - TaskID: uuid.NewString(), SessionID: sess.ID, - Audience: audience.Principal, AuthorityScopes: scopeLimits{}.project(held, scopes), AttributionTeamIDs: teams, - InstallationID: state.InstallationID(sess.Organization, audience.Module), TTL: ttl, - }) -} - -// forWorkload: owner = actor = the module; scopes = the binding's headless -// scopes ∩ asked; the binding names the audience, so the request's must agree. -func (i *Issuer) forWorkload(ctx context.Context, credential, org, bindingID, operation string, audience state.Manifest, asked []*basev0.WorkScopeV1, ttl time.Duration) (string, *basev0.WorkContextV1, error) { - module, err := i.state.VerifyWorkload(ctx, credential) - if err != nil { - return "", nil, err - } - if bindingID == "" { - return "", nil, fmt.Errorf("%w: a workload issue names the binding it exercises", ErrRefused) - } - if org == "" { - organizations := i.state.ActiveInstallationOrganizations(module.Module) - switch len(organizations) { - case 0: - return "", nil, fmt.Errorf("%w: module %q has no active installation; a workload must name the organization of the work item", ErrRefused, module.Module) - case 1: - org = organizations[0] - default: - return "", nil, fmt.Errorf("%w: module %q has ambiguous active installations across organizations; a workload must name the organization of the work item", ErrRefused, module.Module) - } - } - fullID := org + "/" + module.Module + "/" + bindingID - binding, ok := module.Bindings[bindingID] - if !ok { - return "", nil, fmt.Errorf("%w: %s declares no binding %q", workcontext.ErrNoBinding, module.Module, bindingID) - } - if binding.Audience != audience.Principal { - return "", nil, fmt.Errorf("%w: binding %s is addressed to %s, not %s", ErrRefused, bindingID, binding.Audience, audience.Principal) - } - through, err := scopesFor(binding, operation, "headless") - if err != nil { - return "", nil, err - } - scopes, err := grant(asked, through) - if err != nil { - return "", nil, err - } - token, signed, err := i.authority.Start(ctx, workcontext.StartInput{ - TenantID: org, OrganizationID: org, - OwnerPrincipalID: module.Principal, OwnerPrincipalKind: "service", - TaskID: uuid.NewString(), Audience: audience.Principal, AuthorityScopes: scopeLimits{}.project(through, scopes), - InstallationID: state.InstallationID(org, module.Module), OperationBindingID: fullID, TTL: ttl, - }) - if err != nil { - return "", nil, err - } - if signed.GetSeal().GetPrincipalEpoch() != module.Generation || signed.GetOperationBinding().GetRevision() != module.Generation { - return "", nil, fmt.Errorf("%w: workload binding changed during issuance", ErrRefused) - } - return token, signed, nil -} - -// forInstallation: owner = actor = the installation's agent; scopes = asked ∩ -// the installation's role ∩ the audience's ceiling. Its solution authenticates -// with its own workload credential; an installation ID is not a credential. -func (i *Issuer) forInstallation(ctx context.Context, id, org, credential string, audience state.Manifest, asked []*basev0.WorkScopeV1, ttl time.Duration) (string, *basev0.WorkContextV1, error) { - actor, err := i.state.VerifyWorkload(ctx, credential) - if err != nil { - return "", nil, err - } - inst, held, revision, err := i.state.InstallationScopes(id, audience.Principal) - if err != nil { - return "", nil, err - } - if org != "" && org != inst.Organization { - return "", nil, fmt.Errorf("%w: installation %s belongs to organization %q, not %q", state.ErrForbidden, id, inst.Organization, org) - } - if actor.Kind != "solution" || actor.Module != inst.Target { - return "", nil, fmt.Errorf("%w: installation issuance requires the installed solution's workload", state.ErrForbidden) - } - - scopes, err := grant(asked, held) - if err != nil { - return "", nil, err - } - token, signed, err := i.startAtRevision(ctx, revision, workcontext.StartInput{ - TenantID: inst.Organization, OrganizationID: inst.Organization, - OwnerPrincipalID: inst.AgentPrincipal(), OwnerPrincipalKind: "agent", OwnerAgentID: inst.Target, - TaskID: uuid.NewString(), Audience: audience.Principal, AuthorityScopes: scopeLimits{}.project(held, scopes), - InstallationID: inst.ID, TTL: ttl, - }) - if err != nil { - return "", nil, err - } - // The installation's agent epoch does not identify its caller workload. - // Fence the authenticated solution generation separately, as workload Issue - // does, so a delivery in between authentication and mint cannot revive it. - generation, err := i.state.PrincipalEpoch(ctx, actor.Principal) - if err != nil { - return "", nil, err - } - if generation != actor.Generation { - return "", nil, fmt.Errorf("%w: installation workload changed during issuance", ErrRefused) - } - return token, signed, nil -} - -// startAtRevision binds the permission reads to the revision Core stamps. -// A concurrent grant change must not turn old scopes into fresh authority. -// A change after Core reads the revision is rejected by ordinary verification. -func (i *Issuer) startAtRevision(ctx context.Context, revision uint64, in workcontext.StartInput) (string, *basev0.WorkContextV1, error) { - token, signed, err := i.authority.Start(ctx, in) - if err != nil { - return "", nil, err - } - if signed.GetAuthorizationRevision() != revision { - return "", nil, fmt.Errorf("%w: permissions changed during issuance", ErrRefused) - } - return token, signed, nil + return authority.Workload{Module: m.Name}, nil } -// child: one more hop, inherited from the owner. The parent is verified for its -// own audience and must be addressed to the module acting; the actor proves itself -// and names the binding it exercises, addressed to the hop's audience. The child -// is owned by the parent's owner and carries the parent's actors and then this -// one. Human and installation owners supply current target authority through the -// binding. A service owner carries only its existing authority. Stored -// delegations remain complete ceilings. Per-kind parent limits also persist -// across audiences; carried caps are never effective off-target grants. -func (i *Issuer) child(ctx context.Context, parent, actorCredential, bindingID, operation string, audience state.Manifest, asked []*basev0.WorkScopeV1, ttl time.Duration) (string, *basev0.WorkContextV1, error) { - actor, err := i.state.VerifyWorkload(ctx, actorCredential) - if err != nil { - return "", nil, fmt.Errorf("a child hop names the module acting: %w", err) - } - if bindingID == "" { - return "", nil, fmt.Errorf("%w: a module's hop names the binding it exercises", ErrRefused) - } - binding, declared := actor.Bindings[bindingID] - if !declared { - return "", nil, fmt.Errorf("%w: operation binding %q is not one %s declares", state.ErrUnknown, bindingID, actor.Module) - } - if binding.Audience != audience.Principal { - return "", nil, fmt.Errorf("%w: binding %q is addressed to %s, not %s", ErrRefused, bindingID, binding.Audience, audience.Principal) - } - decoded, err := workcontext.Decode(parent) +// AuthorizeTransfer decides whether an existing resource joins a solution +// principal, the store asking with the person's capability. +func (i *Issuer) AuthorizeTransfer(ctx context.Context, req *authorityv1.AuthorizeTransferRequest) (*authorityv1.AuthorizeTransferResponse, error) { + w, err := i.workload(ctx, req.GetActor().GetCredential(), "a transfer is asked by the module serving the kind") if err != nil { - return "", nil, err - } - verified, err := i.verifier(decoded.Context().GetAudience()).Verify(ctx, parent) - if err != nil { - return "", nil, err - } - claims := verified.Context() - if claims.GetAudience() != actor.Principal { - return "", nil, fmt.Errorf("%w: %s hops only on a token addressed to it; this one is addressed to %s", ErrRefused, actor.Module, claims.GetAudience()) - } - var held, sourceCeiling []*basev0.WorkScopeV1 - sourceBounded := false - if claims.GetOwnerPrincipalKind() == "human" { - held, sourceCeiling, sourceBounded, err = i.personThroughSolutions(claims.GetOwnerPrincipalId(), audience, solutionPrincipals(claims, actor.Principal)) - } else { - held, err = i.ownerHolds(claims, audience) - if claims.GetOwnerPrincipalKind() != "agent" { - held = verified.EffectiveScopes() - } - } - if err != nil { - return "", nil, err - } - through, err := scopesFor(binding, operation, "invoke") - if err != nil { - return "", nil, err - } - ceiling := state.Intersect(held, through) - if sourceBounded { - ceiling = state.Intersect(ceiling, sourceCeiling) - } - ceiling, err = i.delegationCeiling(claims, ceiling) - if err != nil { - return "", nil, err - } - limits := parentScopeLimits(claims) - ceiling = limits.constrain(ceiling) - if len(ceiling) == 0 { - return "", nil, fmt.Errorf("%w: %s holds nothing at %s through binding %q", ErrRefused, claims.GetOwnerPrincipalId(), audience.Module, bindingID) - } - scopes := ceiling - if len(asked) > 0 { - if !state.Attenuates(asked, ceiling) { - return "", nil, fmt.Errorf("%w: a child widens what its owner holds through binding %q", ErrRefused, bindingID) - } - scopes = state.Normalize(asked) - } - authorityScopes := limits.project(through, scopes) - // The child never outlives its parent, on the authority's own clock. - now := time.Now() - if i.authority.Now != nil { - now = i.authority.Now() - } - if remaining := time.Unix(claims.GetExpiresAtUnix(), 0).Sub(now); remaining < ttl { - ttl = remaining - } - if ttl <= 0 { - return "", nil, fmt.Errorf("%w: the parent has expired", ErrRefused) - } - tenant := claims.GetTenantId() - hops := append([]*basev0.WorkActorV1(nil), claims.GetActorChain()...) - // The chain is rebuilt for the new audience, and the token the final hop is - // derived from carries the parent's own session, so the child descends from - // the context it was asked from (its parent_session_id is that session), as - // a caller holding the parent checks. - parentSession := claims.GetSessionId() - rootSession := "" - if len(hops) == 0 { - rootSession = parentSession - } - token, _, err := i.authority.Start(ctx, workcontext.StartInput{ - TenantID: tenant, OrganizationID: claims.GetOrganizationId(), - OwnerPrincipalID: claims.GetOwnerPrincipalId(), OwnerPrincipalKind: claims.GetOwnerPrincipalKind(), OwnerAgentID: claims.GetOwnerAgentId(), - TaskID: claims.GetTaskId(), Audience: audience.Principal, AuthorityScopes: authorityScopes, AttributionTeamIDs: claims.GetAttributionTeamIds(), - InstallationID: state.InstallationID(tenant, actor.Module), SessionID: rootSession, TTL: ttl, ReplayPolicy: workcontext.ReplayIdempotent, - }) - if err != nil { - return "", nil, err - } - for n := 0; n <= len(hops); n++ { - current, err := i.verifier(audience.Principal).Verify(ctx, token) - if err != nil { - return "", nil, fmt.Errorf("verify the hop a child is derived from: %w", err) - } - in := workcontext.ChildInput{GrantedScopes: scopes, Audience: audience.Principal, TTL: ttl, ReplayPolicy: workcontext.ReplayIdempotent} - if n < len(hops) { - in.GrantedScopes = authorityScopes - in.PrincipalID, in.PrincipalKind, in.DelegationID = hops[n].GetPrincipalId(), hops[n].GetPrincipalKind(), hops[n].GetDelegationId() - if n == len(hops)-1 { - in.SessionID = parentSession - } - } else { - in.PrincipalID, in.PrincipalKind, in.DelegationID = actor.Principal, "service", claims.GetSessionId() - in.OperationBindingID = tenant + "/" + actor.Module + "/" + bindingID - } - var signed *basev0.WorkContextV1 - token, signed, err = i.authority.Child(ctx, current, in) - if err != nil { - return "", nil, err - } - if n == len(hops) { - if signed.GetOperationBinding().GetRevision() != actor.Generation { - return "", nil, fmt.Errorf("%w: actor binding changed during issuance", ErrRefused) - } - // Reconstruction must not refresh a parent across revocation. Check - // the original capability after the final child has been signed. - if err := i.verifier(claims.GetAudience()).Recheck(ctx, verified); err != nil { - return "", nil, err - } - return token, signed, nil - } - } - return "", nil, fmt.Errorf("%w: no hop was derived", ErrRefused) -} - -// scopesFor is what a binding may do for one operation: invoke, headless or -// lookup, or def when the request names none. -func scopesFor(b state.Binding, operation, def string) ([]*basev0.WorkScopeV1, error) { - if operation == "" { - operation = def - } - switch operation { - case "invoke": - return b.InvokeScopes, nil - case "headless": - return b.HeadlessScopes, nil - case "lookup": - return b.LookupScopes, nil - } - return nil, fmt.Errorf("%w: operation %q is none of invoke, headless and lookup", ErrRefused, operation) -} - -// forDelegation issues for a delegation's grantor through the module it was -// granted to. No live capability is presented: the delegation must stand, the -// actor must be its module, and the scopes are what was delegated, cut to what -// the grantor holds at the audience now and to the binding. The capability is -// the grantor's, with the module as actor and the delegation on its hop. -func (i *Issuer) forDelegation(ctx context.Context, id, actorCredential, bindingID, operation string, audience state.Manifest, asked []*basev0.WorkScopeV1, ttl time.Duration) (string, *basev0.WorkContextV1, error) { - d, err := i.state.DelegationByID(id) - if err != nil { - return "", nil, err - } - if d.Revoked { - return "", nil, fmt.Errorf("%w: delegation %s is revoked", ErrRefused, id) - } - revision, err := i.state.AuthorizationRevision(ctx, d.Organization) - if err != nil { - return "", nil, err - } - actor, err := i.state.VerifyWorkload(ctx, actorCredential) - if err != nil { - return "", nil, fmt.Errorf("an issue from a delegation names the module acting: %w", err) - } - if actor.Module != d.Module { - return "", nil, fmt.Errorf("%w: delegation %s is granted to %s, not %s", ErrRefused, id, d.Module, actor.Module) - } - if bindingID == "" { - return "", nil, fmt.Errorf("%w: an issue from a delegation names the binding it exercises", ErrRefused) - } - binding, declared := actor.Bindings[bindingID] - if !declared { - return "", nil, fmt.Errorf("%w: operation binding %q is not one %s declares", state.ErrUnknown, bindingID, actor.Module) - } - if binding.Audience != audience.Principal { - return "", nil, fmt.Errorf("%w: binding %q is addressed to %s, not %s", ErrRefused, bindingID, binding.Audience, audience.Principal) - } - through, err := scopesFor(binding, operation, "invoke") - if err != nil { - return "", nil, err - } - held, _, _, err := i.personThroughSolutions(d.Grantor, audience, []string{actor.Principal}) - if err != nil { - return "", nil, err - } - ceiling := state.Intersect(state.Intersect(d.WorkScopes(), held), through) - if len(ceiling) == 0 { - return "", nil, fmt.Errorf("%w: delegation %s grants nothing %s still holds at %s through binding %q", ErrRefused, id, d.Grantor, audience.Module, bindingID) - } - scopes, err := grant(asked, ceiling) - if err != nil { - return "", nil, err - } - token, _, err := i.startAtRevision(ctx, revision, workcontext.StartInput{ - TenantID: d.Organization, OrganizationID: d.Organization, - OwnerPrincipalID: d.Grantor, OwnerPrincipalKind: "human", - TaskID: uuid.NewString(), Audience: audience.Principal, AuthorityScopes: scopeLimits{}.project(through, scopes), - InstallationID: state.InstallationID(d.Organization, actor.Module), TTL: ttl, ReplayPolicy: workcontext.ReplayIdempotent, - }) - if err != nil { - return "", nil, err - } - current, err := i.verifier(audience.Principal).Verify(ctx, token) - if err != nil { - return "", nil, fmt.Errorf("verify the capability a delegation's hop is derived from: %w", err) - } - token, signed, err := i.authority.Child(ctx, current, workcontext.ChildInput{ - PrincipalID: actor.Principal, PrincipalKind: "service", DelegationID: d.ID, - GrantedScopes: scopes, Audience: audience.Principal, TTL: ttl, ReplayPolicy: workcontext.ReplayIdempotent, - OperationBindingID: d.Organization + "/" + actor.Module + "/" + bindingID, - }) - if err != nil { - return "", nil, err + return nil, err } - // Start may have observed a newer revision than the initial delegation - // read. Recheck after signing so that revocation cannot be laundered into - // a fresh capability; a later revocation invalidates its signed revision. - d, err = i.state.DelegationByID(id) + d, err := i.svc.AuthorizeTransfer(ctx, authority.TransferRequest{Token: req.GetToken(), Store: w, Kind: req.GetResourceKind(), + ResourceID: req.GetResourceId(), From: req.GetFromOwnerId(), To: req.GetToPrincipalId()}) if err != nil { - return "", nil, err - } - if d.Revoked { - return "", nil, fmt.Errorf("%w: delegation %s is revoked", ErrRefused, id) - } - if signed.GetOperationBinding().GetRevision() != actor.Generation { - return "", nil, fmt.Errorf("%w: actor binding changed during issuance", ErrRefused) + return nil, err } - return token, signed, nil + return &authorityv1.AuthorizeTransferResponse{PrincipalId: d.PrincipalID, AuthorizedBy: d.AuthorizedBy}, nil } // Delegate records the signed-in person's grant to a module. func (i *Issuer) Delegate(ctx context.Context, module string, scopes []*basev0.WorkScopeV1, purpose string) (state.Delegation, error) { - if err := refuseOwners(scopes); err != nil { - return state.Delegation{}, err - } person, err := i.signedIn(ctx) if err != nil { return state.Delegation{}, err } - return i.state.Delegate(person, module, scopes, purpose) + return i.svc.Delegate(ctx, person, module, scopes, purpose) } // GetDelegation reads a delegation for the module it is granted to or for its // grantor. Anyone else learns nothing, not even that it exists. func (i *Issuer) GetDelegation(ctx context.Context, id, actorCredential string) (state.Delegation, error) { - d, err := i.state.DelegationByID(id) - if err != nil { - return state.Delegation{}, err - } if actorCredential != "" { - if actor, err := i.state.VerifyWorkload(ctx, actorCredential); err == nil && actor.Module == d.Module { - return d, nil + w, err := i.workload(ctx, actorCredential, "a delegation is read by its module") + if err != nil { + return state.Delegation{}, err } - } else if person, err := i.signedIn(ctx); err == nil && person == d.Grantor { - return d, nil + return i.svc.Delegation(ctx, id, nil, &w) + } + person, err := i.signedIn(ctx) + if err != nil { + return state.Delegation{}, err } - return state.Delegation{}, fmt.Errorf("%w: delegation %q", state.ErrUnknown, id) + return i.svc.Delegation(ctx, id, &person, nil) } // RevokeDelegation ends one of the signed-in person's delegations. @@ -692,68 +225,15 @@ func (i *Issuer) RevokeDelegation(ctx context.Context, id string) (state.Delegat if err != nil { return state.Delegation{}, err } - return i.state.RevokeDelegation(person, id) + return i.svc.RevokeDelegation(ctx, person, id) } -func (i *Issuer) signedIn(ctx context.Context) (string, error) { +func (i *Issuer) signedIn(ctx context.Context) (authority.Person, error) { sessionID, ok := sessionOf(ctx) if !ok { - return "", fmt.Errorf("%w: a delegation is made by a signed-in person; the call carries no session", ErrRefused) + return authority.Person{}, fmt.Errorf("%w: a delegation is made by a signed-in person; the call carries no session", ErrRefused) } - sess, err := i.state.Session(sessionID) - if err != nil { - return "", err - } - return sess.Person, nil -} - -// ownerHolds is what the owner of a verified context holds at the audience now: -// a person's roles, or an installation's role, each within what the audience -// serves. -func (i *Issuer) ownerHolds(claims *basev0.WorkContextV1, audience state.Manifest) ([]*basev0.WorkScopeV1, error) { - owner := claims.GetOwnerPrincipalId() - switch claims.GetOwnerPrincipalKind() { - case "human": - return i.state.PersonScopes(owner, audience.Principal) - case "agent": - _, scopes, _, err := i.state.InstallationScopes(strings.TrimSuffix(owner, "/agent"), audience.Principal) - return scopes, err - } - // A module's own context inherits nothing more at the next audience: its - // child carries what it was already delegated. - return nil, nil -} - -// grant is asked ∩ held, or held when nothing was asked; asking beyond held is refused. -func grant(asked, held []*basev0.WorkScopeV1) ([]*basev0.WorkScopeV1, error) { - if len(asked) == 0 { - if len(held) == 0 { - return nil, fmt.Errorf("%w: the owner holds nothing at this audience", ErrRefused) - } - for _, sc := range held { - if slices.Contains(sc.GetActions(), state.AnyAction) { - return nil, fmt.Errorf("%w: the audience declares %s without its actions; ask for the scopes you need", ErrRefused, sc.GetResourceKind()) - } - } - return held, nil - } - if !state.Attenuates(asked, held) { - return nil, fmt.Errorf("%w: asked beyond what the owner holds", ErrRefused) - } - return state.Normalize(asked), nil -} - -// refuseOwners refuses an owner-selected scope by name: this authority's -// algebra cannot compare owners, so honouring one would issue the whole kind, -// wider than asked. It mints owner-selected scopes only once it issues through -// the owner-aware core, behind the audience's owner_aware declaration. -func refuseOwners(scopes []*basev0.WorkScopeV1) error { - for _, sc := range scopes { - if sc.GetResourceOwnerId() != "" { - return fmt.Errorf("%w: %s names resource owner %q; this authority issues no owner-selected scope yet", ErrRefused, sc.GetResourceKind(), sc.GetResourceOwnerId()) - } - } - return nil + return i.state.Caller(sessionID) } // ToWire converts core's message to the interface's, byte for byte. @@ -777,26 +257,3 @@ func FromWire(in []*authorityv1.WorkScopeV1) []*basev0.WorkScopeV1 { } return out } - -// audienceOfBinding answers the audience of the binding a workload or a hop -// names, from the module its credential attests. -func (i *Issuer) audienceOfBinding(ctx context.Context, req *authorityv1.IssueRequest) (string, error) { - var credential string - switch as := req.GetAs().(type) { - case *authorityv1.IssueRequest_Workload: - credential = as.Workload.GetCredential() - case *authorityv1.IssueRequest_ParentToken, *authorityv1.IssueRequest_Delegation: - credential = req.GetActor().GetCredential() - default: - return "", fmt.Errorf("%w: only a workload or a hop names a binding in place of an audience", ErrRefused) - } - module, err := i.state.VerifyWorkload(ctx, credential) - if err != nil { - return "", err - } - binding, declared := module.Bindings[req.GetOperationBindingId()] - if !declared { - return "", fmt.Errorf("%w: operation binding %q is not one %s declares", state.ErrUnknown, req.GetOperationBindingId(), module.Module) - } - return binding.Audience, nil -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/issuer_test.go b/modules/authority/services/local/code/pkg/business/issuer/issuer_test.go index 09482e6d..e5dfdfe8 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/issuer_test.go +++ b/modules/authority/services/local/code/pkg/business/issuer/issuer_test.go @@ -236,7 +236,7 @@ func TestAnUninstallRefusesAnOutstandingTokenOnTheNextCheck(t *testing.T) { if _, err := k.Issuer.Check(ctx, out.GetToken(), "wiki", ""); err != nil { t.Fatalf("before: %v", err) } - if err := k.State.Revoke(wiki.ID); err != nil { + if err := k.State.Uninstall(wiki.ID); err != nil { t.Fatal(err) } _, err = k.Issuer.Check(ctx, out.GetToken(), "wiki", "") @@ -343,7 +343,7 @@ func TestAWorkloadInfersOnlyOneActiveInstallingOrganization(t *testing.T) { t.Run(tc.name, func(t *testing.T) { k := testkit.New(t) for _, org := range tc.revoke { - if err := k.State.Revoke(state.InstallationID(org, "documents")); err != nil { + if err := k.State.Uninstall(state.InstallationID(org, "documents")); err != nil { t.Fatal(err) } } @@ -445,7 +445,7 @@ func TestKeysLetAnIndependentVerifierCheckWhatWasIssued(t *testing.T) { verifier := &workcontext.Verifier{ Issuer: keys.GetIssuer(), Audience: "documents", Keys: map[string]ed25519.PublicKey{keys.GetKeys()[0].GetKeyId(): ed25519.PublicKey(keys.GetKeys()[0].GetPublicKey())}, - Revisions: k.State, Seals: k.State, Grants: k.State, Replay: workcontext.NewMemoryReplayStore(), Now: func() time.Time { return k.Now }, + Revisions: k.State.Service().Revisions(), Seals: k.State.Service().Seals(), Replay: workcontext.NewMemoryReplayStore(), Now: func() time.Time { return k.Now }, } if _, err := verifier.Verify(ctx, out.GetToken()); err != nil { t.Fatalf("an independent core verifier refused what we issued: %v", err) @@ -510,11 +510,11 @@ func TestABindingNamingIssueMayLeaveItsAudienceToTheBinding(t *testing.T) { // declarations arrive. The developer role never invents actions from a wildcard. func TestTheDeveloperRoleHoldsWhatEachComposedModuleServes(t *testing.T) { k := testkit.New(t) - held, err := k.State.PersonScopes("dev", "records") + held, err := k.PersonScopes(t, "dev", "records") if err != nil || len(held) != 2 { t.Fatalf("dev at records: %v %v", held, err) } - if _, err := k.State.ApplyManifest(state.Manifest{Module: "probe", Principal: "probe", Kind: "module", Generation: 1, + if _, err := k.State.ApplyManifest(state.Manifest{Name: "probe", Principal: "probe", Kind: "module", Generation: 1, ResourceKinds: []*basev0.WorkScopeV1{testkit.Scope("probe.items", state.AnyAction)}}); err != nil { t.Fatal(err) } diff --git a/modules/authority/services/local/code/pkg/business/issuer/owner_refused_test.go b/modules/authority/services/local/code/pkg/business/issuer/owner_refused_test.go deleted file mode 100644 index 202dd251..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/owner_refused_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package issuer_test - -import ( - "errors" - "strings" - "testing" - - "gateway/pkg/business/issuer" - "gateway/pkg/business/testkit" - authorityv1 "gateway/pkg/gen/host/authority/v1" - base "github.com/codefly-dev/core/generated/go/codefly/base/v0" -) - -// The wire carries resource_owner_id, and this authority cannot compare -// owners yet: an owner-selected ask is refused by name, never issued as the -// whole kind, and so is an owner-selected delegation. -func TestAnOwnerSelectedAskIsRefusedNeverWidened(t *testing.T) { - k := testkit.New(t) - owned := []*authorityv1.WorkScopeV1{{ResourceKind: "records.records", Actions: []string{"read"}, ResourceOwnerId: "principal-u"}} - _, err := k.Issuer.Issue(person(t, k, "fixture-maya"), &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "records", Scopes: owned}) - if !errors.Is(err, issuer.ErrRefused) || !strings.Contains(err.Error(), "resource owner") { - t.Fatalf("an owner-selected ask: %v", err) - } - _, err = k.Issuer.Delegate(person(t, k, "fixture-remi"), "org-directory", - []*base.WorkScopeV1{{ResourceKind: "documents", Actions: []string{"read"}, ResourceOwnerId: "principal-u"}}, "owned") - if !errors.Is(err, issuer.ErrRefused) || !strings.Contains(err.Error(), "resource owner") { - t.Fatalf("an owner-selected delegation: %v", err) - } -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/parent_rows_test.go b/modules/authority/services/local/code/pkg/business/issuer/parent_rows_test.go index d512cc5b..bc35bdba 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/parent_rows_test.go +++ b/modules/authority/services/local/code/pkg/business/issuer/parent_rows_test.go @@ -9,58 +9,6 @@ import ( base "github.com/codefly-dev/core/generated/go/codefly/base/v0" ) -func TestRootPreservesActionSpecificResources(t *testing.T) { - for _, shape := range []string{"person", "workload", "installation", "delegation"} { - t.Run(shape, func(t *testing.T) { - k, _ := solutionRoleKit(t, testkit.Scope("documents", "read", "write")) - rows := []*base.WorkScopeV1{ - {ResourceKind: "documents", Actions: []string{"read"}, ResourceIds: []string{"a"}}, - {ResourceKind: "documents", Actions: []string{"write"}, ResourceIds: []string{"b"}}, - } - ctx := person(t, k, "fixture-remi") - req := &authorityv1.IssueRequest{Audience: "documents", Scopes: scopes(rows...)} - switch shape { - case "person": - req.As = &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}} - case "workload": - module := testkit.OrgDirectory(2) - binding := module.Bindings["documents"] - binding.HeadlessScopes = []*base.WorkScopeV1{testkit.Scope("documents", "read", "write")} - module.Bindings["documents"] = binding - if _, err := k.State.ApplyManifest(module); err != nil { - t.Fatal(err) - } - req.As = &authorityv1.IssueRequest_Workload{Workload: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "org-directory")}} - req.OrganizationId, req.OperationBindingId = "acme", "documents" - case "installation": - req.As = &authorityv1.IssueRequest_Installation{Installation: &authorityv1.InstallationRef{InstallationId: state.InstallationID("acme", "wiki")}} - req.Actor = &authorityv1.WorkloadCredential{Credential: k.Credential(t, "wiki")} - case "delegation": - d, err := k.Issuer.Delegate(ctx, "org-directory", rows, "action-specific resources") - if err != nil { - t.Fatal(err) - } - req.As = &authorityv1.IssueRequest_Delegation{Delegation: &authorityv1.DelegationRef{DelegationId: d.ID}} - req.Actor = &authorityv1.WorkloadCredential{Credential: k.Credential(t, "org-directory")} - req.OperationBindingId = "documents" - } - out, err := k.Issuer.Issue(ctx, req) - if err != nil { - t.Fatal(err) - } - if _, err = k.Issuer.Check(t.Context(), out.Token, "documents", ""); err != nil { - t.Fatal(err) - } - for action, id := range map[string]string{"read": "a", "write": "b"} { - grants, err := k.Issuer.ListResources(t.Context(), out.Token, "documents", "documents", action) - if err != nil || len(grants) != 1 || grants[0].ID != id { - t.Fatalf("%s must grant only %s: grants=%v err=%v", action, id, grants, err) - } - } - }) - } -} - func TestMalformedAskDoesNotGainAuthority(t *testing.T) { for _, scope := range []*authorityv1.WorkScopeV1{nil, {}, {ResourceKind: "modelservice.profiles"}, {Actions: []string{"invoke"}}} { k := testkit.New(t) diff --git a/modules/authority/services/local/code/pkg/business/issuer/resources_test.go b/modules/authority/services/local/code/pkg/business/issuer/resources_test.go index dfaca8f2..b98fb72b 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/resources_test.go +++ b/modules/authority/services/local/code/pkg/business/issuer/resources_test.go @@ -3,6 +3,7 @@ package issuer_test import ( "context" "errors" + "github.com/codefly-dev/authority/libraries/authority-core/go/authority" "testing" resources "github.com/codefly-dev/authority/libraries/resources/go" @@ -24,8 +25,15 @@ func issued(t *testing.T, k *testkit.Kit, fixtureToken string, scopes []*authori return out.GetToken() } -func covered(t *testing.T, grants []resources.Grant) *resources.Matcher { +func covered(t *testing.T, owned []authority.Grant) *resources.Matcher { t.Helper() + var grants []resources.Grant + for _, g := range owned { + if g.Owner != "" { + t.Fatalf("an organisation grant names an owner: %+v", g) + } + grants = append(grants, g.Grant) + } m, err := resources.Compile(grants) if err != nil { t.Fatal(err) diff --git a/modules/authority/services/local/code/pkg/business/issuer/scenario_test.go b/modules/authority/services/local/code/pkg/business/issuer/scenario_test.go deleted file mode 100644 index afa11244..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/scenario_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package issuer_test - -// The primer's section 4, one scenario, one hop at a time, run against the local -// authority with the same cast: Acme, Maya on Finance, Rémi the engineer, the -// wiki, documents, model, runtime, records and the org-directory page. Each -// step asserts what the primer says the token carries and what refuses today. - -import ( - "context" - "errors" - "strings" - "testing" - - "github.com/codefly-dev/core/workcontext" - - "gateway/pkg/business/issuer" - "gateway/pkg/business/state" - "gateway/pkg/business/testkit" - authorityv1 "gateway/pkg/gen/host/authority/v1" -) - -func TestTheScenarioOneHopAtATime(t *testing.T) { - k := testkit.New(t) - ctx := context.Background() - documents := &authorityv1.WorkloadCredential{Credential: k.Credential(t, "documents")} - var maya state.Session - var asMaya context.Context - var mayaAtDocuments string - - t.Run("1 Maya signs in", func(t *testing.T) { - var err error - maya, err = k.State.SignIn("fixture-maya") - if err != nil || maya.Organization != "acme" || maya.Teams[0] != "finance" { - t.Fatalf("Maya's session: %v %+v", err, maya) - } - asMaya = issuer.WithSession(ctx, maya.ID) // the bearer every call of hers carries - // Her session says who she is, not what she may do in documents. - if len(maya.Roles) != 1 || maya.Roles[0] != "viewer" { - t.Fatalf("roles = %v", maya.Roles) - } - }) - - t.Run("2 Maya sees the wiki in her navigation", func(t *testing.T) { - nav := k.State.Entitled(maya.Person) - if len(nav) != 1 || nav[0].Target != "wiki" { - t.Fatalf("Maya's navigation = %+v", nav) - } - // Rémi on engineering sees it too: the installation reaches both teams. - if nav := k.State.Entitled("remi"); len(nav) != 1 || nav[0].Target != "wiki" { - t.Fatalf("Rémi's navigation = %+v", nav) - } - }) - - t.Run("3 the wiki reads a page body from documents on Maya's behalf", func(t *testing.T) { - out, err := k.Issuer.Issue(asMaya, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, - Audience: "documents", Scopes: scopes(testkit.Scope("documents", "read")), - }) - if err != nil { - t.Fatal(err) - } - wc := out.GetContext() - // owner = actor = Maya; audience documents; scopes = roles ∩ ceiling; sealed to documents at Acme. - if wc.GetOwnerPrincipalId() != "maya" || len(wc.GetActorChain()) != 0 || wc.GetAudience() != "documents" || - wc.GetSeal().GetInstallationId() != state.InstallationID("acme", "documents") || strings.Join(wc.GetAuthorityScopes()[0].GetActions(), ",") != "read" { - t.Fatalf("step 3 token = %v", wc) - } - if _, err := k.Issuer.Check(ctx, out.GetToken(), "documents", ""); err != nil { - t.Fatalf("documents checks it: %v", err) - } - mayaAtDocuments = out.GetToken() - // Refuses today: a role the solution cannot declare. Here the viewer role - // is declared in the fixture and write is refused by name. - _, err = k.Issuer.Issue(asMaya, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, - Audience: "documents", Scopes: scopes(testkit.Scope("documents", "write")), - }) - if !errors.Is(err, issuer.ErrRefused) { - t.Fatalf("a viewer writing: %v", err) - } - }) - - t.Run("4 documents embeds the page through the model gateway, for itself", func(t *testing.T) { - out, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Workload{Workload: documents}, Audience: "model", OrganizationId: "acme", OperationBindingId: "model", - }) - if err != nil { - t.Fatal(err) - } - wc := out.GetContext() - if wc.GetOwnerPrincipalId() != "documents" || wc.GetOwnerPrincipalKind() != "service" || wc.GetAudience() != "model" || - wc.GetOperationBinding().GetBindingId() != "acme/documents/model" || wc.GetSeal().GetInstallationId() != state.InstallationID("acme", "documents") { - t.Fatalf("step 4 token = %v", wc) - } - if _, err := k.Issuer.Check(ctx, out.GetToken(), "model", ""); err != nil { - t.Fatalf("model checks it: %v", err) - } - // Refuses today, the audience: the composition typed modelservice in a second key. - _, err = k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Workload{Workload: documents}, Audience: "modelservice", OrganizationId: "acme", OperationBindingId: "model", - }) - if !errors.Is(err, state.ErrUnknown) || !strings.Contains(err.Error(), `"modelservice" is not composed`) { - t.Fatalf("the audience typo: %v", err) - } - // Refuses today, all or nothing: the envelope forgot runtime-admission and - // documents lost its whole entry. Here documents keeps both bindings. - if _, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Workload{Workload: documents}, Audience: "runtime", OrganizationId: "acme", OperationBindingId: "runtime-admission", - }); err != nil { - t.Fatalf("the second binding: %v", err) - } - }) - - t.Run("5 documents fans out only for a runtime-entitled person", func(t *testing.T) { - // Maya has no runtime grant. Her documents read must not stand in for it. - _, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_ParentToken{ParentToken: mayaAtDocuments}, Actor: documents, - Audience: "runtime", OperationBindingId: "runtime-admission", TtlSeconds: 60, - }) - if !errors.Is(err, issuer.ErrRefused) { - t.Fatalf("Maya's absent runtime authority: %v", err) - } - parent, err := k.Issuer.Issue(person(t, k, "fixture-dev"), &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "documents", - }) - if err != nil { - t.Fatal(err) - } - child, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_ParentToken{ParentToken: parent.Token}, Actor: documents, - Audience: "runtime", OperationBindingId: "runtime-admission", TtlSeconds: 60, - }) - if err != nil { - t.Fatal(err) - } - wc := child.GetContext() - if wc.GetOwnerPrincipalId() != "dev" || len(wc.GetActorChain()) != 1 || wc.GetActorChain()[0].GetPrincipalId() != "documents" || - wc.GetAudience() != "runtime" || wc.GetExpiresAtUnix()-k.Now.Unix() < 60 || wc.GetExpiresAtUnix()-k.Now.Unix() > 62 { - t.Fatalf("step 5 child = %v", wc) - } - if _, err := k.Issuer.Check(ctx, child.GetToken(), "runtime", ""); err != nil { - t.Fatalf("the runtime checks the child: %v", err) - } - if len(effectiveScopes(wc)) != 1 || actions(wc, "runtime.task") != "admit,read" { - t.Fatalf("runtime child carries scopes outside its binding: %v", effectiveScopes(wc)) - } - // Even the developer cannot carry documents scope through this binding. - _, err = k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_ParentToken{ParentToken: parent.Token}, Actor: documents, - Audience: "runtime", OperationBindingId: "runtime-admission", Scopes: scopes(testkit.Scope("documents", "write")), - }) - if !errors.Is(err, issuer.ErrRefused) { - t.Fatalf("widening through the runtime: %v", err) - } - }) - - t.Run("6 overnight, the wiki reconciles, as itself", func(t *testing.T) { - out, err := k.Issuer.Issue(ctx, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Installation{Installation: &authorityv1.InstallationRef{InstallationId: state.InstallationID("acme", "wiki")}}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "wiki")}, Audience: "wiki", - }) - if err != nil { - t.Fatal(err) - } - wc := out.GetContext() - // owner = actor = the wiki at Acme; scopes = what Acme consented to at install (viewer) ∩ the solution's own ceiling. No downstream grant is implied. - if wc.GetOwnerPrincipalId() != state.InstallationID("acme", "wiki")+"/agent" || wc.GetOwnerAgentId() != "wiki" || wc.GetSeal().GetInstallationId() != state.InstallationID("acme", "wiki") || - strings.Join(wc.GetAuthorityScopes()[0].GetActions(), ",") != "read" { - t.Fatalf("step 6 token = %v", wc) - } - // Acme's administrator uninstalls the wiki at 10:00; the 09:58 token is refused at 10:00:01. - if err := k.State.Revoke(state.InstallationID("acme", "wiki")); err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.Check(ctx, out.GetToken(), "wiki", ""); err == nil { - t.Fatal("an uninstalled wiki still acted") - } - }) - - t.Run("7 Maya opens a module page that reads records", func(t *testing.T) { - // Today a module page has no path to a records-audience token. Here the - // page is a module, Maya holds records read through her role, and the - // page asks for Maya at records: the module-for-a-person shape. - out, err := k.Issuer.Issue(asMaya, &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "records", - }) - if err != nil { - t.Fatal(err) - } - wc := out.GetContext() - kinds := map[string]string{} - for _, sc := range wc.GetAuthorityScopes() { - kinds[sc.GetResourceKind()] = strings.Join(sc.GetActions(), ",") - } - if kinds["records.records"] != "read" || kinds["records.types"] != "read" || wc.GetSeal().GetInstallationId() != state.InstallationID("acme", "records") { - t.Fatalf("step 7 token = %v", wc) - } - // Rémi, an editor, is also issued write on records; the UI follows the token. - remi, _ := k.State.SignIn("fixture-remi") - out, err = k.Issuer.Issue(issuer.WithSession(ctx, remi.ID), &authorityv1.IssueRequest{ - As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "records", - }) - if err != nil { - t.Fatal(err) - } - for _, sc := range out.GetContext().GetAuthorityScopes() { - if sc.GetResourceKind() == "records.records" && strings.Join(sc.GetActions(), ",") != "read,write" { - t.Fatalf("Rémi at records = %v", sc) - } - } - }) - - t.Run("8 Rémi runs all of this on a laptop", func(t *testing.T) { - // Everything above ran with no accounts service, no store, no envelope, no - // anchor file: one launcher key, one fixture, one issuer. The only thing a - // laptop and a cell share is the code that issued and checked. - keys := k.Issuer.Keys() - if keys.GetIssuer() == "" || len(keys.GetKeys()) != 1 { - t.Fatalf("keys = %v", keys) - } - if _, err := k.State.VerifyWorkload(ctx, documents.GetCredential()); err != nil { - t.Fatalf("the launcher's word for documents: %v", err) - } - if _, err := k.Issuer.Check(ctx, workcontext.FixtureForeignToken(), "documents", ""); err == nil { - t.Fatal("a token that is not ours verified") - } - }) -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/scope_limits.go b/modules/authority/services/local/code/pkg/business/issuer/scope_limits.go deleted file mode 100644 index 0560d1d5..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/scope_limits.go +++ /dev/null @@ -1,80 +0,0 @@ -package issuer - -import ( - "sort" - - "gateway/pkg/business/state" - base "github.com/codefly-dev/core/generated/go/codefly/base/v0" - "google.golang.org/protobuf/proto" -) - -// scopeLimits are lasting bounds on visited kinds. A zero-action scope is an -// explicit denial; an absent kind has not been constrained. Never normalize the -// entire set through the grant helpers, which intentionally omit empty grants. -type scopeLimits map[string][]*base.WorkScopeV1 - -func parentScopeLimits(parent *base.WorkContextV1) scopeLimits { - sets := [][]*base.WorkScopeV1{parent.GetAuthorityScopes()} - for _, actor := range parent.GetActorChain() { - sets = append(sets, actor.GetGrantedScopes()) - } - limits := scopeLimits{} - for _, scopes := range sets { - for kind, rows := range scopesByKind(scopes) { - if previous, ok := limits[kind]; ok { - limits[kind] = state.Intersect(previous, rows) - } else { - limits[kind] = rows - } - } - } - return limits -} - -func (limits scopeLimits) constrain(scopes []*base.WorkScopeV1) []*base.WorkScopeV1 { - var out []*base.WorkScopeV1 - for _, scope := range scopes { - if bound, ok := limits[scope.GetResourceKind()]; ok { - out = append(out, state.Intersect([]*base.WorkScopeV1{scope}, bound)...) - } else { - out = append(out, scope) - } - } - return out -} - -// project updates only the current binding's kinds, retaining other audience -// caps. An omitted or disjoint target kind stays explicitly denied downstream. -func (limits scopeLimits) project(domain, granted []*base.WorkScopeV1) []*base.WorkScopeV1 { - for _, scope := range domain { - limits[scope.GetResourceKind()] = nil - } - for kind, rows := range scopesByKind(granted) { - limits[kind] = rows - } - out := make([]*base.WorkScopeV1, 0, len(limits)) - kinds := make([]string, 0, len(limits)) - for kind := range limits { - kinds = append(kinds, kind) - } - sort.Strings(kinds) - for _, kind := range kinds { - rows := state.Normalize(limits[kind]) - if len(rows) == 0 { - rows = []*base.WorkScopeV1{{ResourceKind: kind}} - } - out = append(out, rows...) - } - return out -} - -// Keep every action/resource row together. Combining their actions and ids -// separately would authorize the cross product; keeping only one loses grants. -func scopesByKind(scopes []*base.WorkScopeV1) scopeLimits { - grouped := scopeLimits{} - for _, scope := range scopes { - kind := scope.GetResourceKind() - grouped[kind] = append(grouped[kind], proto.CloneOf(scope)) - } - return grouped -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/solution_roles.go b/modules/authority/services/local/code/pkg/business/issuer/solution_roles.go deleted file mode 100644 index a16f7be9..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/solution_roles.go +++ /dev/null @@ -1,90 +0,0 @@ -package issuer - -import ( - "gateway/pkg/business/state" - resources "github.com/codefly-dev/authority/libraries/resources/go" - basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" - "slices" -) - -// solutionPrincipals reads provenance already verified by Core. A module hop -// retains its predecessors; it cannot drop a solution's permission source. -func solutionPrincipals(wc *basev0.WorkContextV1, current string) []string { - out := []string{current} - for _, actor := range wc.GetActorChain() { - if actor.GetPrincipalKind() == "service" { - out = append(out, actor.GetPrincipalId()) - } - } - return out -} - -// solutionCeiling combines the current role of each solution in the verified -// path. The boolean distinguishes no solution from a solution granting nothing. -func (i *Issuer) solutionCeiling(person string, principals []string) ([]*basev0.WorkScopeV1, bool, error) { - seen := map[string]bool{} - var ceiling []*basev0.WorkScopeV1 - bounded := false - for _, principal := range principals { - if seen[principal] { - continue - } - seen[principal] = true - m, err := i.state.ModuleByPrincipal(principal) - if err != nil { - return nil, false, err - } - if m.Kind != "solution" { - continue - } - role, err := i.state.PersonSolutionScopes(person, principal) - if err != nil { - return nil, false, err - } - if bounded { - ceiling = state.Intersect(ceiling, role) - } else { - ceiling = role - } - bounded = true - } - return ceiling, bounded, nil -} - -// personThroughSolutions selects solution roles instead of organization roles -// when a solution acts. A target solution additionally requires its own grant. -func (i *Issuer) personThroughSolutions(person string, audience state.Manifest, principals []string) (held, ceiling []*basev0.WorkScopeV1, bounded bool, err error) { - ceiling, bounded, err = i.solutionCeiling(person, principals) - if err != nil { - return - } - if !bounded { - held, err = i.state.PersonScopes(person, audience.Principal) - return - } - held = state.Intersect(ceiling, audience.ResourceKinds) - if audience.Kind == "solution" { - var target []*basev0.WorkScopeV1 - target, err = i.state.PersonScopes(person, audience.Principal) - held = state.Intersect(held, target) - } - return -} - -// roleResources projects only exact IDs or whole-kind grants, as solution roles -// declare them. Organization prefix/pattern grants keep their existing reader. -func roleResources(scopes []*basev0.WorkScopeV1, kind, action string) []resources.Grant { - var grants []resources.Grant - for _, scope := range scopes { - if scope.GetResourceKind() != kind || (!slices.Contains(scope.GetActions(), action) && !slices.Contains(scope.GetActions(), state.AnyAction)) { - continue - } - if len(scope.GetResourceIds()) == 0 { - return []resources.Grant{{All: true}} - } - for _, id := range scope.GetResourceIds() { - grants = append(grants, resources.Grant{ID: id}) - } - } - return grants -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/solution_roles_test.go b/modules/authority/services/local/code/pkg/business/issuer/solution_roles_test.go deleted file mode 100644 index 55bb3dcb..00000000 --- a/modules/authority/services/local/code/pkg/business/issuer/solution_roles_test.go +++ /dev/null @@ -1,383 +0,0 @@ -package issuer_test - -import ( - "context" - "errors" - "maps" - "testing" - - "gateway/pkg/business/issuer" - "gateway/pkg/business/state" - "gateway/pkg/business/testkit" - authorityv1 "gateway/pkg/gen/host/authority/v1" - basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" - "github.com/codefly-dev/core/workcontext" -) - -func solutionRoleKit(t *testing.T, roleScope *basev0.WorkScopeV1) (*testkit.Kit, state.Manifest) { - t.Helper() - k := testkit.New(t) - m := testkit.Wiki(2) - m.Bindings = map[string]state.Binding{ - "documents": {ID: "documents", Audience: "documents", InvokeScopes: []*basev0.WorkScopeV1{testkit.Scope("documents", "read", "write")}}, - "directory": {ID: "directory", Audience: "org-directory", InvokeScopes: []*basev0.WorkScopeV1{testkit.Scope("directory.entry", "read")}}, - } - m.Roles["viewer"] = append(m.Roles["viewer"], roleScope, testkit.Scope("directory.entry", "read")) - if _, err := k.State.ApplyManifest(m); err != nil { - t.Fatal(err) - } - return k, m -} - -// Prepare parents and stored consent before an issuance-interleaving test starts. -func solutionPersonRequest(t *testing.T, k *testkit.Kit, who, path string, ask []*authorityv1.WorkScopeV1) (context.Context, *authorityv1.IssueRequest, error) { - t.Helper() - ctx := person(t, k, "fixture-"+who) - req := &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "wiki"} - if path == "binding" { - req.OperationBindingId, req.Scopes = "documents", ask - return ctx, req, nil - } - if path == "delegation" { - d, err := k.Issuer.Delegate(ctx, "wiki", issuer.FromWire(ask), "solution role test") - if err != nil { - return nil, nil, err - } - return context.Background(), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Delegation{Delegation: &authorityv1.DelegationRef{DelegationId: d.ID}}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "wiki")}, Audience: "documents", OperationBindingId: "documents", Scopes: ask}, nil - } - root, err := k.Issuer.Issue(ctx, req) - if err != nil { - return nil, nil, err - } - actor := "wiki" - if path == "later module" { - root, err = k.Issuer.Issue(context.Background(), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_ParentToken{ParentToken: root.Token}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "wiki")}, OperationBindingId: "directory", Audience: "org-directory", Scopes: scopes(testkit.Scope("directory.entry", "read"))}) - if err != nil { - return nil, nil, err - } - actor = "org-directory" - } - return context.Background(), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_ParentToken{ParentToken: root.Token}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, actor)}, OperationBindingId: "documents", Audience: "documents", Scopes: ask}, nil -} - -func solutionPersonIssue(t *testing.T, k *testkit.Kit, who, path string, ask []*authorityv1.WorkScopeV1) (*authorityv1.IssueResponse, error) { - t.Helper() - ctx, req, err := solutionPersonRequest(t, k, who, path, ask) - if err != nil { - return nil, err - } - return k.Issuer.Issue(ctx, req) -} - -func TestSolutionRoleIsThePermissionSourceForEveryPersonPath(t *testing.T) { - for _, path := range []string{"binding", "child", "later module", "delegation"} { - for _, tc := range []struct { - who, role, ask string - allowed bool - }{ - {"remi", "read", "read", true}, {"remi", "read", "write", false}, - {"maya", "write", "write", true}, - } { - t.Run(path+"/"+tc.who+"/"+tc.ask, func(t *testing.T) { - k, _ := solutionRoleKit(t, testkit.Scope("documents", tc.role)) - out, err := solutionPersonIssue(t, k, tc.who, path, scopes(testkit.Scope("documents", tc.ask))) - if !tc.allowed { - if err == nil { - t.Fatalf("recovered organization write outside solution role: %v", out.Context) - } - return - } - if err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.Check(context.Background(), out.Token, out.Context.Audience, ""); err != nil { - t.Fatal(err) - } - grants, err := k.Issuer.ListResources(context.Background(), out.Token, out.Context.Audience, "documents", tc.ask) - if err != nil || !covered(t, grants).All() { - t.Fatalf("unrestricted role lost resources: %v %v", grants, err) - } - if tc.who == "maya" { - _, err := k.Issuer.Issue(person(t, k, "fixture-maya"), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "documents", Scopes: scopes(testkit.Scope("documents", "write"))}) - if !errors.Is(err, issuer.ErrRefused) { - t.Fatalf("solution role leaked into independent module access: %v", err) - } - } - }) - } - } -} - -func TestSolutionBindingCannotBootstrapWithoutEntitlement(t *testing.T) { - for _, path := range []string{"binding", "child", "later module", "delegation"} { - for _, unavailable := range []string{"no team", "installation revoked", "other organization only"} { - t.Run(path+"/"+unavailable, func(t *testing.T) { - k, _ := solutionRoleKit(t, testkit.Scope("documents", "write")) - who := "dev" // Keep the organisation union, but grant only finance below. - if unavailable == "no team" { - if err := k.State.Revoke(state.InstallationID("acme", "wiki")); err != nil { - t.Fatal(err) - } - inst, err := k.State.Install(context.Background(), "acme", "wiki", "viewer", "alex") - if err != nil { - t.Fatal(err) - } - if _, err := k.State.GrantToTeam(context.Background(), "acme", inst.ID, "finance"); err != nil { - t.Fatal(err) - } - } - if unavailable != "no team" { - who = "remi" - if err := k.State.Revoke(state.InstallationID("acme", "wiki")); err != nil { - t.Fatal(err) - } - if unavailable == "installation revoked" { - if err := k.State.Revoke(state.InstallationID("globex", "wiki")); err != nil { - t.Fatal(err) - } - } - } - if out, err := solutionPersonIssue(t, k, who, path, scopes(testkit.Scope("documents", "write"))); err == nil { - t.Fatalf("organization grant bootstrapped solution access: %v", out.Context) - } - }) - } - } -} - -func TestSolutionInstallationRevokesLaterPersonHops(t *testing.T) { - for _, path := range []string{"binding", "child", "later module", "delegation"} { - t.Run(path, func(t *testing.T) { - k, _ := solutionRoleKit(t, testkit.Scope("documents", "write")) - out, err := solutionPersonIssue(t, k, "maya", path, scopes(testkit.Scope("documents", "write"))) - if err != nil { - t.Fatal(err) - } - if err := k.State.Revoke(state.InstallationID("acme", "wiki")); err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.Check(context.Background(), out.Token, out.Context.Audience, ""); err == nil { - t.Fatal("person work survived source solution uninstall") - } - if _, err := k.Issuer.ListResources(context.Background(), out.Token, out.Context.Audience, "documents", "write"); err == nil { - t.Fatal("resource read survived source solution uninstall") - } - }) - } -} - -func TestSolutionRoleResourcesSurviveLaterHopsAndRevocation(t *testing.T) { - for _, path := range []string{"child", "later module", "delegation"} { - t.Run(path, func(t *testing.T) { - held := testkit.Scope("documents", "write") - held.ResourceIds = []string{"one"} - k, m := solutionRoleKit(t, held) - var issued *authorityv1.IssueResponse - for _, tc := range []struct { - ids []string - allowed bool - }{{[]string{"one"}, true}, {[]string{"two"}, false}, {nil, false}} { - out, err := solutionPersonIssue(t, k, "maya", path, []*authorityv1.WorkScopeV1{{ResourceKind: "documents", Actions: []string{"write"}, ResourceIds: tc.ids}}) - if !tc.allowed { - if err == nil { - t.Fatalf("widened role resource IDs %v", tc.ids) - } - continue - } - if err != nil { - t.Fatal(err) - } - grants, err := k.Issuer.ListResources(context.Background(), out.Token, "documents", "documents", "write") - if err != nil { - t.Fatal(err) - } - if got := covered(t, grants); !got.Matches("one") || got.Matches("two") || got.All() { - t.Fatalf("role resources not preserved: %v", grants) - } - issued = out - } - m.Generation++ - m.Roles = maps.Clone(m.Roles) - m.Roles["viewer"] = []*basev0.WorkScopeV1{testkit.Scope("wiki.page", "read"), testkit.Scope("directory.entry", "read")} - if _, err := k.State.ApplyManifest(m); err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.Check(context.Background(), issued.Token, "documents", ""); !errors.Is(err, workcontext.ErrRevoked) { - t.Fatalf("old role authority survived removal: %v", err) - } - if _, err := solutionPersonIssue(t, k, "maya", path, []*authorityv1.WorkScopeV1{{ResourceKind: "documents", Actions: []string{"write"}, ResourceIds: []string{"one"}}}); err == nil { - t.Fatal("fresh work recovered removed role") - } - }) - } -} - -func TestSolutionDelegationRechecksCurrentRoleAtUse(t *testing.T) { - for _, change := range []string{"role narrowed", "delegation revoked"} { - t.Run(change, func(t *testing.T) { - k, m := solutionRoleKit(t, testkit.Scope("documents", "write")) - ctx := person(t, k, "fixture-maya") - d, err := k.Issuer.Delegate(ctx, "wiki", []*basev0.WorkScopeV1{testkit.Scope("documents", "write")}, "use-time validation") - if err != nil { - t.Fatal(err) - } - req := &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Delegation{Delegation: &authorityv1.DelegationRef{DelegationId: d.ID}}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, "wiki")}, Audience: "documents", OperationBindingId: "documents", Scopes: scopes(testkit.Scope("documents", "write"))} - before, err := k.Issuer.Issue(context.Background(), req) - if err != nil { - t.Fatal(err) - } - if change == "role narrowed" { - m.Generation++ - m.Roles = maps.Clone(m.Roles) - m.Roles["viewer"] = []*basev0.WorkScopeV1{testkit.Scope("wiki.page", "read"), testkit.Scope("documents", "read")} - if _, err := k.State.ApplyManifest(m); err != nil { - t.Fatal(err) - } - } else if _, err := k.Issuer.RevokeDelegation(ctx, d.ID); err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.Issue(context.Background(), req); err == nil { - t.Fatal("stored consent recovered write") - } - if _, err := k.Issuer.Check(context.Background(), before.Token, "documents", ""); err == nil { - t.Fatal("previously issued delegation survived revocation") - } - }) - } -} - -func TestSolutionPersonRoleChangeCannotRaceIssuance(t *testing.T) { - for _, path := range []string{"binding", "child", "later module", "delegation"} { - for _, stage := range []string{"before_resolution", "before_mint", "during_mint", "after_issue"} { - t.Run(path+"/"+stage, func(t *testing.T) { - k, m := solutionRoleKit(t, testkit.Scope("documents", "write")) - ctx, req, err := solutionPersonRequest(t, k, "maya", path, scopes(testkit.Scope("documents", "write"))) - if err != nil { - t.Fatal(err) - } - issue := func() (*authorityv1.IssueResponse, error) { return k.Issuer.Issue(ctx, req) } - if _, err := issue(); err != nil { - t.Fatal(err) - } - out, err := interleaveIssuance(t, stage, func() { - m.Generation++ - m.Roles = maps.Clone(m.Roles) - m.Roles["viewer"] = []*basev0.WorkScopeV1{testkit.Scope("wiki.page", "read"), testkit.Scope("documents", "read"), testkit.Scope("directory.entry", "read")} - if _, err := k.State.ApplyManifest(m); err != nil { - t.Fatal(err) - } - }, issue) - if err != nil { - return - } - if _, err := k.Issuer.Check(context.Background(), out.Token, out.Context.Audience, ""); err == nil { - t.Fatal("stale solution write was signed with current authorization") - } - }) - } - } -} - -func TestSolutionInstallerAndTeamRolesRemainDistinctSources(t *testing.T) { - k := testkit.New(t) - m := testkit.Proposals(2) - m.Bindings = map[string]state.Binding{"documents": {ID: "documents", Audience: "documents", InvokeScopes: []*basev0.WorkScopeV1{testkit.Scope("documents", "read", "write")}}} - m.Roles[state.InstallerRole] = append(m.Roles[state.InstallerRole], testkit.Scope("documents", "write")) - m.Roles["viewer"] = append(m.Roles["viewer"], testkit.Scope("documents", "read")) - if _, err := k.State.ApplyManifest(m); err != nil { - t.Fatal(err) - } - inst, err := k.State.Install(context.Background(), "acme", "proposals", "viewer", "alex") - if err != nil { - t.Fatal(err) - } - check := func(who string, actions ...string) { - t.Helper() - got, err := k.State.PersonSolutionScopes(who, "proposals") - if err != nil { - t.Fatal(err) - } - for _, action := range []string{"read", "write"} { - want := false - for _, expected := range actions { - want = want || action == expected - } - if state.Attenuates([]*basev0.WorkScopeV1{testkit.Scope("documents", action)}, got) != want { - t.Fatalf("%s incorrectly holds %s: %v", who, action, got) - } - } - } - check("alex", "write") - check("maya") - check("zoe") - if _, err := k.State.GrantToTeam(context.Background(), "acme", inst.ID, "finance"); err != nil { - t.Fatal(err) - } - check("alex", "read", "write") - check("maya", "read") - check("remi") - check("zoe") -} - -func TestEverySolutionInPersonChainRemainsACeiling(t *testing.T) { - for _, narrow := range []string{"wiki", "proposals"} { - t.Run(narrow, func(t *testing.T) { - k, a := solutionRoleKit(t, testkit.Scope("documents", "read", "write")) - b := testkit.Proposals(2) - b.Bindings = a.Bindings - b.Roles["viewer"] = append(b.Roles["viewer"], testkit.Scope("documents", "read", "write"), testkit.Scope("directory.entry", "read")) - if narrow == "proposals" { - b.Roles["viewer"][1] = testkit.Scope("documents", "read") - } - if _, err := k.State.ApplyManifest(b); err != nil { - t.Fatal(err) - } - a.Generation++ - a.Roles = maps.Clone(a.Roles) - a.Roles["viewer"] = []*basev0.WorkScopeV1{testkit.Scope("wiki.page", "read"), testkit.Scope("documents", "read", "write"), testkit.Scope("directory.entry", "read"), testkit.Scope("proposals.item", "read")} - if narrow == "wiki" { - a.Roles["viewer"][1] = testkit.Scope("documents", "read") - } - a.Bindings = maps.Clone(a.Bindings) - a.Bindings["tools"] = state.Binding{ID: "tools", Audience: "proposals", InvokeScopes: []*basev0.WorkScopeV1{testkit.Scope("proposals.item", "read")}} - if _, err := k.State.ApplyManifest(a); err != nil { - t.Fatal(err) - } - inst, err := k.State.Install(context.Background(), "acme", "proposals", "viewer", "alex") - if err != nil { - t.Fatal(err) - } - if _, err := k.State.GrantToTeam(context.Background(), "acme", inst.ID, "finance"); err != nil { - t.Fatal(err) - } - root, err := k.Issuer.Issue(person(t, k, "fixture-maya"), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "wiki"}) - if err != nil { - t.Fatal(err) - } - hop := func(parent *authorityv1.IssueResponse, actor, binding, target, kind, action string) (*authorityv1.IssueResponse, error) { - return k.Issuer.Issue(context.Background(), &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_ParentToken{ParentToken: parent.Token}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, actor)}, OperationBindingId: binding, Audience: target, Scopes: scopes(testkit.Scope(kind, action))}) - } - tools, err := hop(root, "wiki", "tools", "proposals", "proposals.item", "read") - if err != nil { - t.Fatal(err) - } - later, err := hop(tools, "proposals", "directory", "org-directory", "directory.entry", "read") - if err != nil { - t.Fatal(err) - } - for _, parent := range []*authorityv1.IssueResponse{tools, later} { - actor := parent.Context.Audience - read, err := hop(parent, actor, "documents", "documents", "documents", "read") - if err != nil { - t.Fatal(err) - } - if _, err := k.Issuer.Check(context.Background(), read.Token, "documents", ""); err != nil { - t.Fatal(err) - } - if _, err := hop(parent, actor, "documents", "documents", "documents", "write"); !errors.Is(err, issuer.ErrRefused) { - t.Fatalf("%s recovered write beyond %s: %v", actor, narrow, err) - } - } - }) - } -} diff --git a/modules/authority/services/local/code/pkg/business/issuer/solution_tools_test.go b/modules/authority/services/local/code/pkg/business/issuer/solution_tools_test.go index 1c1279f8..a0d53991 100644 --- a/modules/authority/services/local/code/pkg/business/issuer/solution_tools_test.go +++ b/modules/authority/services/local/code/pkg/business/issuer/solution_tools_test.go @@ -128,11 +128,11 @@ func TestInstallationWorkloadDeliveryCannotChangeDuringIssuance(t *testing.T) { if _, err := k.State.ApplyManifest(m); err != nil { t.Fatal(err) } - inst, err := k.State.Install(context.Background(), "acme", m.Module, "viewer", "alex") + inst, err := k.State.Install(context.Background(), "acme", m.Name, "viewer", "alex") if err != nil { t.Fatal(err) } - req := &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Installation{Installation: &authorityv1.InstallationRef{InstallationId: inst.ID}}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, m.Module)}, Audience: m.Principal} + req := &authorityv1.IssueRequest{As: &authorityv1.IssueRequest_Installation{Installation: &authorityv1.InstallationRef{InstallationId: inst.ID}}, Actor: &authorityv1.WorkloadCredential{Credential: k.Credential(t, m.Name)}, Audience: m.Principal} _, err = interleaveIssuance(t, stage, func() { m.Generation++ if _, err := k.State.ApplyManifest(m); err != nil { diff --git a/modules/authority/services/local/code/pkg/business/state/approvals.go b/modules/authority/services/local/code/pkg/business/state/approvals.go index ccd7b3b7..5a356dd1 100644 --- a/modules/authority/services/local/code/pkg/business/state/approvals.go +++ b/modules/authority/services/local/code/pkg/business/state/approvals.go @@ -5,7 +5,6 @@ import ( "fmt" "sync" - basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" "github.com/google/uuid" ) @@ -106,8 +105,3 @@ func (s *MemoryApprovals) find(installation, policy, digest string) (Approval, b } return Approval{}, false } - -// HoldsAction is whether held carries action on kind. -func HoldsAction(held []*basev0.WorkScopeV1, kind, action string) bool { - return holdsAction(held, kind, action) -} diff --git a/modules/authority/services/local/code/pkg/business/state/composition.go b/modules/authority/services/local/code/pkg/business/state/composition.go index 0e98caf2..e37f6df6 100644 --- a/modules/authority/services/local/code/pkg/business/state/composition.go +++ b/modules/authority/services/local/code/pkg/business/state/composition.go @@ -67,6 +67,7 @@ func (s *State) ApplyComposition(ctx context.Context, data []byte) ([]string, er for key, role := range roles { s.roles[key] = role } + s.refreshRolesLocked() s.mu.Unlock() if len(doc.Installations) > 0 || len(doc.Modules) > 0 { setup, err := yaml.Marshal(fixtureFile{Installations: doc.Installations, Modules: doc.Modules}) @@ -186,15 +187,16 @@ func (s *State) checkComposedSetup(doc compositionFile) ([]string, error) { var lines []string for _, i := range doc.Installations { owner, ok := s.people[i.Owner] + _, known := s.orgs[i.Organization] switch { case i.Target == "" || i.Role == "": return nil, fmt.Errorf("an installation names no target or no role") - case s.orgs[i.Organization] == "": + case !known: return nil, fmt.Errorf("installation of %s: %q is not an organisation the fixture defines", i.Target, i.Organization) case !ok || owner.Organization != i.Organization: return nil, fmt.Errorf("installation of %s: %q is not a person of %s in the fixture", i.Target, i.Owner, i.Organization) } - lines = append(lines, fmt.Sprintf("installs %s at %s as %s, for %v", i.Target, i.Organization, i.Role, i.Teams)) + lines = append(lines, fmt.Sprintf("installs %s at %s as %s, granting %s the installer role and %v the role %s", i.Target, i.Organization, i.Role, i.Owner, i.Teams, i.Role)) } for _, m := range doc.Modules { if !kindGrammar.MatchString(m.Module) || len(m.Kinds) == 0 { diff --git a/modules/authority/services/local/code/pkg/business/state/composition_test.go b/modules/authority/services/local/code/pkg/business/state/composition_test.go index e202c25a..d0e01042 100644 --- a/modules/authority/services/local/code/pkg/business/state/composition_test.go +++ b/modules/authority/services/local/code/pkg/business/state/composition_test.go @@ -26,7 +26,7 @@ func fixtureOnly(t *testing.T) *testkit.Kit { if err := k.State.LoadFixture(context.Background(), data); err != nil { t.Fatal(err) } - ledger := state.Manifest{Module: "ledger", Principal: "ledger", Kind: "module", Generation: 1, + ledger := state.Manifest{Name: "ledger", Principal: "ledger", Kind: "module", Generation: 1, Methods: map[string]state.MethodPolicy{ "/ledger/Read": {Requires: []*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "read")}}, "/ledger/Write": {Requires: []*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "write")}}, @@ -43,7 +43,7 @@ func fixtureOnly(t *testing.T) *testkit.Kit { func TestCompositionGrantsFixtureRolesWhatItsModuleServes(t *testing.T) { k := fixtureOnly(t) for _, person := range []string{"maya", "remi", "alex", "zoe"} { - if held, _ := k.State.PersonScopes(person, "ledger"); len(held) != 0 { + if held, _ := k.PersonScopes(t, person, "ledger"); len(held) != 0 { t.Fatalf("the fixture alone grants %s %v at ledger", person, held) } } @@ -71,18 +71,18 @@ roles: "remi": testkit.Scope("ledger.entry", "read", "write"), "alex": testkit.Scope("ledger.entry", "approve"), } { - held, err := k.State.PersonScopes(person, "ledger") + held, err := k.PersonScopes(t, person, "ledger") if err != nil || !state.Attenuates([]*basev0.WorkScopeV1{scope}, held) { t.Errorf("%s holds %v at ledger (%v), want %v", person, held, err, scope) } } - if held, _ := k.State.PersonScopes("alex", "ledger"); state.Attenuates([]*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "write")}, held) { + if held, _ := k.PersonScopes(t, "alex", "ledger"); state.Attenuates([]*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "write")}, held) { t.Error("a grant to the editor reached the admin") } if err := k.State.AdmitMethod("ledger", "/ledger/Approve", mustHeld(t, k, "remi")); err == nil { t.Error("the editor was admitted to the admin's method") } - grants, err := k.State.ResourceGrants("maya", "ledger", "ledger.entry", "read") + grants, err := k.ResourceGrants(t, "maya", "ledger", "ledger.entry", "read") if err != nil || len(grants) != 1 || grants[0].Prefix != "public-" { t.Errorf("the viewer's grant keeps its resources: %v %v", grants, err) } @@ -94,7 +94,7 @@ roles: func mustHeld(t *testing.T, k *testkit.Kit, person string) []*basev0.WorkScopeV1 { t.Helper() - held, err := k.State.PersonScopes(person, "ledger") + held, err := k.PersonScopes(t, person, "ledger") if err != nil { t.Fatal(err) } diff --git a/modules/authority/services/local/code/pkg/business/state/delegation_fixture_test.go b/modules/authority/services/local/code/pkg/business/state/delegation_fixture_test.go index 26ce8df6..19e1d475 100644 --- a/modules/authority/services/local/code/pkg/business/state/delegation_fixture_test.go +++ b/modules/authority/services/local/code/pkg/business/state/delegation_fixture_test.go @@ -6,6 +6,8 @@ import ( "strings" "testing" + "github.com/codefly-dev/authority/libraries/authority-core/go/authority" + "gateway/pkg/business/testkit" ) @@ -30,11 +32,11 @@ func TestDelegationFixtureRetainsExactIDsAndRefusesRoleMatchers(t *testing.T) { if err != nil { t.Fatal(err) } - d, err := k.State.DelegationByID("narrow") + d, err := k.State.Service().Delegation(context.Background(), "narrow", nil, &authority.Workload{Module: "org-directory"}) if err != nil { t.Fatal(err) } - scopes := d.WorkScopes() + scopes := d.Scopes if len(scopes) != 1 || !slices.Equal(scopes[0].GetResourceIds(), []string{"a"}) { t.Fatalf("fixture restriction lost: %v", scopes) } diff --git a/modules/authority/services/local/code/pkg/business/state/delivery.go b/modules/authority/services/local/code/pkg/business/state/delivery.go index 5ba16163..d943c7a8 100644 --- a/modules/authority/services/local/code/pkg/business/state/delivery.go +++ b/modules/authority/services/local/code/pkg/business/state/delivery.go @@ -20,13 +20,11 @@ type appliedDelivery struct { completion chan struct{} } -// pendingInstallation follows one fixture intent across failed callbacks and -// superseding deliveries. Its fields are guarded by State.mu; revision fences -// an explicit uninstall/reinstall and done prevents replay of completed grants. +// pendingInstallation is one fixture installation waiting on a delivery; done +// prevents its replay once performed. Guarded by State.mu. type pendingInstallation struct { FixtureInstallation - revision uint64 - done bool + done bool } // ApplyDelivered atomically admits and projects a verified carrier. Coordinate @@ -91,11 +89,13 @@ func (s *State) admitDeliveryLocked(coordinate string, carrier *solutionhost.Del if candidate.Removed { // An unknown tombstone must not erase an unrelated transitional sketch. if previous == nil { - if _, exists := s.modules[candidate.Alias]; exists { + if _, exists := s.Module(candidate.Alias); exists { return nil, fmt.Errorf("%w: withdrawal has no admitted binding for %s", ErrForbidden, candidate.Alias) } } - s.withdrawLocked(candidate.Alias) + if err := s.withdrawLocked(candidate.Alias); err != nil { + return nil, err + } } else { ids, pending, err := s.applyManifestLocked(m) if err != nil { @@ -142,7 +142,7 @@ func (s *State) finishDelivery(ctx context.Context, manifest *solutionhost.Modul return nil } -func (s *State) finishFixtureInstalls(ctx context.Context, manifest *solutionhost.ModuleManifest, pending []*pendingInstallation) error { +func (s *State) finishFixtureInstalls(ctx context.Context, _ *solutionhost.ModuleManifest, pending []*pendingInstallation) error { for _, i := range pending { if err := ctx.Err(); err != nil { return err @@ -153,53 +153,25 @@ func (s *State) finishFixtureInstalls(ctx context.Context, manifest *solutionhos if done { continue } - inst, err := s.install(ctx, i.Organization, i.Target, i.Role, i.Owner, manifest.Generation, i) - if err != nil { - return fmt.Errorf("fixture installation of %s: %w", i.Target, err) + if err := s.performInstall(ctx, i.FixtureInstallation); err != nil { + return err } s.mu.Lock() - current, live := s.modules[i.Target] - held := s.installations[inst.ID] - if !live || current.Generation != manifest.Generation || held.Status != Active || held.Revision != inst.Revision { - s.mu.Unlock() - return fmt.Errorf("%w: fixture installation changed before team grants", ErrStale) - } - for _, team := range i.Teams { - if !slices.Contains(held.Teams, team) { - held.Teams = append(held.Teams, team) - held.Revision++ - s.revisions[i.Organization]++ - } - } - s.installations[inst.ID] = held i.done = true s.mu.Unlock() } return nil } -func (s *State) withdrawLocked(alias string) { - if previous, ok := s.modules[alias]; ok { - delete(s.principals, previous.Principal) - } - delete(s.modules, alias) +// withdrawLocked removes a module through the core: its installations are +// revoked and the delegations granted to it end. Caller holds mu. +func (s *State) withdrawLocked(alias string) error { s.takePendingInstalls(alias) - for id, installation := range s.installations { - if installation.Target != alias || installation.Status == Revoked { - continue - } - installation.Status = Revoked - installation.Revision++ - s.installations[id] = installation - s.revisions[installation.Organization]++ - } - for id, delegation := range s.delegations { - if delegation.Module != alias || delegation.Revoked { - continue + if _, ok := s.Module(alias); ok { + if err := s.svc.WithdrawModule(context.Background(), alias); err != nil { + return err } - delegation.Revoked = true - s.delegations[id] = delegation - s.revisions[delegation.Organization]++ } - s.refreshDeveloperRoles() + s.refreshRolesLocked() + return nil } diff --git a/modules/authority/services/local/code/pkg/business/state/installation_access.go b/modules/authority/services/local/code/pkg/business/state/installation_access.go deleted file mode 100644 index 8cdd7d65..00000000 --- a/modules/authority/services/local/code/pkg/business/state/installation_access.go +++ /dev/null @@ -1,34 +0,0 @@ -package state - -import ( - "fmt" - "slices" -) - -// RequireAdministrator authenticates the session and reads the person's current -// organization roles, rather than trusting the roles captured at sign-in. -func (s *State) RequireAdministrator(session, org string) error { - s.mu.RLock() - defer s.mu.RUnlock() - sess, ok := s.sessions[session] - if !ok { - return fmt.Errorf("%w: an open session is required", ErrUnauthenticated) - } - person, ok := s.people[sess.Person] - if !ok || person.Organization != org || (!slices.Contains(person.Roles, "admin") && !slices.Contains(person.Roles, "owner")) { - return fmt.Errorf("%w: installation administration requires an administrator or owner of organization %q", ErrForbidden, org) - } - return nil -} - -// RequireInstallation distinguishes an uninstalled audience from a person who -// holds nothing there. The issuer fences this read with authorization revision. -func (s *State) RequireInstallation(org, module, audience string) error { - s.mu.RLock() - defer s.mu.RUnlock() - inst, ok := s.installations[InstallationID(org, module)] - if !ok || inst.Status != Active { - return fmt.Errorf("%w: audience %q is not installed in this organization, so there is no installation to seal the capability to", ErrNotInstalled, audience) - } - return nil -} diff --git a/modules/authority/services/local/code/pkg/business/state/installation_seal_test.go b/modules/authority/services/local/code/pkg/business/state/installation_seal_test.go deleted file mode 100644 index 7954f0e6..00000000 --- a/modules/authority/services/local/code/pkg/business/state/installation_seal_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package state_test - -import ( - "context" - "errors" - "testing" - - "gateway/pkg/business/state" - "gateway/pkg/business/testkit" - "github.com/codefly-dev/core/workcontext" -) - -func TestInstallationAgentSealIsBoundedToLiveSameOrganizationModules(t *testing.T) { - for _, tc := range []struct { - name, source, org, target string - revoke string - allowed bool - }{ - {name: "same organization module", source: "wiki", org: "acme", target: "documents", allowed: true}, - {name: "own installation", source: "wiki", org: "acme", target: "wiki", allowed: true}, - {name: "foreign organization", source: "wiki", org: "globex", target: "documents"}, - {name: "unknown agent", source: "missing", org: "acme", target: "documents"}, - {name: "module is not a solution agent", source: "org-directory", org: "acme", target: "documents"}, - {name: "revoked source", source: "wiki", org: "acme", target: "documents", revoke: "wiki"}, - {name: "pending source", source: "wiki", org: "acme", target: "documents", revoke: "wiki"}, - {name: "revoked module", source: "wiki", org: "acme", target: "documents", revoke: "documents"}, - {name: "another solution", source: "wiki", org: "acme", target: "proposals"}, - } { - t.Run(tc.name, func(t *testing.T) { - k := testkit.New(t) - if tc.target == "proposals" { - if _, err := k.State.ApplyManifest(testkit.Proposals(1)); err != nil { - t.Fatal(err) - } - if _, err := k.State.Install(context.Background(), "acme", "proposals", "viewer", "alex"); err != nil { - t.Fatal(err) - } - } - if tc.revoke != "" { - if err := k.State.Revoke(state.InstallationID("acme", tc.revoke)); err != nil { - t.Fatal(err) - } - } - if tc.name == "pending source" { - k.State.OnInstall(func(context.Context, state.Installation) error { return errors.New("not ready") }) - inst, err := k.State.Install(context.Background(), "acme", "wiki", "viewer", "alex") - if err == nil || inst.Status != state.Pending { - t.Fatalf("pending source precondition: %+v %v", inst, err) - } - } - target := state.InstallationID(tc.org, tc.target) - got, err := k.State.Seal(context.Background(), state.InstallationID("acme", tc.source)+"/agent", target) - if tc.allowed { - if err != nil || got.InstallationID != target || got.InstallationRevision == 0 { - t.Fatalf("active installation association: %+v %v", got, err) - } - } else if !errors.Is(err, workcontext.ErrNoSeal) { - t.Fatalf("unrelated installation association: %+v %v", got, err) - } - }) - } -} diff --git a/modules/authority/services/local/code/pkg/business/state/permissions.go b/modules/authority/services/local/code/pkg/business/state/permissions.go index 41eb55e7..4de020c1 100644 --- a/modules/authority/services/local/code/pkg/business/state/permissions.go +++ b/modules/authority/services/local/code/pkg/business/state/permissions.go @@ -5,49 +5,82 @@ import ( "slices" "sort" + "github.com/codefly-dev/authority/libraries/authority-core/go/authority" + "github.com/codefly-dev/authority/libraries/authority-core/go/decide" + "github.com/codefly-dev/authority/libraries/authority-core/go/domain" + resources "github.com/codefly-dev/authority/libraries/resources/go" basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" ) -// refreshDeveloperRoles replaces, never accumulates, the local fixture's -// generated union. It does not assign roles to people or change business roles. -// Caller holds mu. Explicit non-method ceilings remain supported. -func (s *State) refreshDeveloperRoles() { +// refreshRolesLocked recomputes the generated developer role from the +// current delivered catalog (replaced, never accumulated) and writes every +// organisation role to the core's store as authored. Caller holds mu. +func (s *State) refreshRolesLocked() { union := s.permissionCatalog() for key, role := range s.roles { - if !role.Generated { - continue + if role.Generated { + role.Scopes = nil + for _, sc := range union { + role.Scopes = append(role.Scopes, Scope{Kind: sc.GetResourceKind(), Actions: slices.Clone(sc.GetActions())}) + } + s.roles[key] = role } - role.Scopes = nil - for _, sc := range union { - role.Scopes = append(role.Scopes, Scope{Kind: sc.GetResourceKind(), Actions: append([]string(nil), sc.GetActions()...)}) + stored := domain.OrgRole{Organization: role.Organization, Name: role.Name} + for _, sc := range role.Scopes { + grants := make([]resources.Grant, 0, len(sc.Resources)) + for _, m := range sc.Resources { + grants = append(grants, m.Grant()) + } + stored.Scopes = append(stored.Scopes, domain.RoleScope{Kind: sc.Kind, Actions: slices.Clone(sc.Actions), Resources: grants}) } - s.roles[key] = role + s.store.PutOrgRole(stored) } } -// permissionCatalog contains concrete declarations only. Caller holds mu. +// RoleScopes is what an organisation role grants as authored, as scopes. +func (s *State) RoleScopes(org, role string) []*basev0.WorkScopeV1 { + s.mu.RLock() + defer s.mu.RUnlock() + var out []*basev0.WorkScopeV1 + for _, sc := range s.roles[org+"/"+role].Scopes { + out = append(out, &basev0.WorkScopeV1{ResourceKind: sc.Kind, Actions: slices.Clone(sc.Actions)}) + } + return Normalize(out) +} + +func (s *State) modules() []Manifest { + var out []Manifest + _ = s.read(func(tx authority.Tx) error { + var err error + out, err = tx.Modules() + return err + }) + return out +} + +// permissionCatalog is every concrete (kind, action) a delivered module serves. func (s *State) permissionCatalog() []*basev0.WorkScopeV1 { var union []*basev0.WorkScopeV1 - for _, m := range s.modules { - if m.Kind != "solution" { - for _, sc := range m.ResourceKinds { - if sc.GetResourceKind() == AnyKind { - continue - } - var actions []string - for _, action := range sc.GetActions() { - if action != AnyAction { - actions = append(actions, action) - } - } - if len(actions) > 0 { - union = append(union, &basev0.WorkScopeV1{ResourceKind: sc.GetResourceKind(), Actions: actions}) + for _, m := range s.modules() { + if m.Kind == domain.KindSolution { + continue + } + for _, sc := range m.ResourceKinds { + if sc.GetResourceKind() == AnyKind { + continue + } + var actions []string + for _, action := range sc.GetActions() { + if action != AnyAction { + actions = append(actions, action) } } + if len(actions) > 0 { + union = append(union, &basev0.WorkScopeV1{ResourceKind: sc.GetResourceKind(), Actions: actions}) + } } } - union = Normalize(union) - return union + return Normalize(union) } // PermissionReport describes a snapshot of delivered generations. Coverage @@ -57,18 +90,22 @@ func (s *State) PermissionReport() []string { s.mu.RLock() defer s.mu.RUnlock() var lines []string + modules := s.modules() catalog := s.permissionCatalog() kinds := map[string]bool{} - for _, m := range s.modules { - if m.Kind == "solution" { + for _, m := range modules { + if m.Kind == domain.KindSolution { continue } for _, sc := range m.ResourceKinds { kinds[sc.GetResourceKind()] = true } } + var orgRoles []domain.OrgRole for key, role := range s.roles { + stored := domain.OrgRole{Organization: role.Organization, Name: key} for _, sc := range role.Scopes { + stored.Scopes = append(stored.Scopes, domain.RoleScope{Kind: sc.Kind, Actions: sc.Actions}) if sc.Kind == AnyKind { continue } @@ -78,39 +115,30 @@ func (s *State) PermissionReport() []string { } if !kinds[sc.Kind] { lines = append(lines, fmt.Sprintf("WARN role %s: %s:%s unresolved in current delivery", key, sc.Kind, action)) - } else if !holdsAction(catalog, sc.Kind, action) { + } else if !HoldsAction(catalog, sc.Kind, action) { lines = append(lines, fmt.Sprintf("WARN role %s: %s:%s is not declared by current delivery", key, sc.Kind, action)) } } } + orgRoles = append(orgRoles, stored) } - for _, m := range s.modules { + for _, m := range modules { if len(m.Methods) == 0 { - lines = append(lines, fmt.Sprintf("WARN %s@%d: no delivered method policies; declared ceilings alone do not admit a call", m.Module, m.Generation)) + lines = append(lines, fmt.Sprintf("WARN %s@%d: no delivered method policies; declared ceilings alone do not admit a call", m.Name, m.Generation)) } for _, sc := range m.ResourceKinds { if sc.GetResourceKind() == AnyKind || slices.Contains(sc.GetActions(), AnyAction) { - lines = append(lines, fmt.Sprintf("WARN %s@%d %s: wildcard ceiling is unresolved; generated developer uses explicit actions only", m.Module, m.Generation, sc.GetResourceKind())) + lines = append(lines, fmt.Sprintf("WARN %s@%d %s: wildcard ceiling is unresolved; generated developer uses explicit actions only", m.Name, m.Generation, sc.GetResourceKind())) } } roles := map[string][]*basev0.WorkScopeV1{} - if m.Kind == "solution" { + if m.Kind == domain.KindSolution { for name, scopes := range m.Roles { roles["installation/"+name] = scopes } } else { - for key, role := range s.roles { - var scopes []*basev0.WorkScopeV1 - for _, sc := range role.Scopes { - if sc.Kind == AnyKind { - for _, kind := range m.ResourceKinds { - scopes = append(scopes, &basev0.WorkScopeV1{ResourceKind: kind.GetResourceKind(), Actions: sc.Actions}) - } - } else { - scopes = append(scopes, &basev0.WorkScopeV1{ResourceKind: sc.Kind, Actions: sc.Actions}) - } - } - roles[key] = Intersect(Normalize(scopes), m.ResourceKinds) + for _, role := range orgRoles { + roles[role.Name] = decide.OrgScopes([]domain.OrgRole{role}, m) } } for method := range m.Methods { @@ -122,10 +150,10 @@ func (s *State) PermissionReport() []string { } else { covered = true } - lines = append(lines, fmt.Sprintf("role %s × %s@%d × %s: %s", role, m.Module, m.Generation, method, result)) + lines = append(lines, fmt.Sprintf("role %s × %s@%d × %s: %s", role, m.Name, m.Generation, method, result)) } if !covered { - lines = append(lines, fmt.Sprintf("WARN %s@%d %s: no single declared role covers this method", m.Module, m.Generation, method)) + lines = append(lines, fmt.Sprintf("WARN %s@%d %s: no single declared role covers this method", m.Name, m.Generation, method)) } } } diff --git a/modules/authority/services/local/code/pkg/business/state/permissions_test.go b/modules/authority/services/local/code/pkg/business/state/permissions_test.go index 80d019f9..925d909f 100644 --- a/modules/authority/services/local/code/pkg/business/state/permissions_test.go +++ b/modules/authority/services/local/code/pkg/business/state/permissions_test.go @@ -11,7 +11,7 @@ import ( func TestGeneratedDeveloperTracksReplacementAndReportsBusinessGaps(t *testing.T) { k := testkit.New(t) - m := state.Manifest{Module: "ledger", Principal: "ledger", Kind: "module", Generation: 1, + m := state.Manifest{Name: "ledger", Principal: "ledger", Kind: "module", Generation: 1, Methods: map[string]state.MethodPolicy{ "/ledger/Write": {Requires: []*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "write")}}, "/ledger/Version": {AnyCheckedCaller: true}, @@ -21,7 +21,7 @@ func TestGeneratedDeveloperTracksReplacementAndReportsBusinessGaps(t *testing.T) if _, err := k.State.ApplyManifest(m); err != nil { t.Fatal(err) } - held, err := k.State.PersonScopes("dev", "ledger") + held, err := k.PersonScopes(t, "dev", "ledger") if err != nil || !state.Attenuates([]*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "write")}, held) { t.Fatalf("developer: %v %v", held, err) } @@ -36,7 +36,7 @@ func TestGeneratedDeveloperTracksReplacementAndReportsBusinessGaps(t *testing.T) if _, err := k.State.ApplyManifest(m); err != nil { t.Fatal(err) } - held, err = k.State.PersonScopes("dev", "ledger") + held, err = k.PersonScopes(t, "dev", "ledger") if err != nil || state.Attenuates([]*basev0.WorkScopeV1{testkit.Scope("ledger.entry", "write")}, held) { t.Fatalf("removed write retained: %v %v", held, err) } @@ -56,7 +56,7 @@ func TestGeneratedDeveloperTracksReplacementAndReportsBusinessGaps(t *testing.T) func TestGeneratedDeveloperNeverCopiesWildcardCarrierCeilings(t *testing.T) { k := testkit.New(t) - m := state.Manifest{Module: "unresolved", Principal: "unresolved", Kind: "module", Generation: 1, + m := state.Manifest{Name: "unresolved", Principal: "unresolved", Kind: "module", Generation: 1, ResourceKinds: []*basev0.WorkScopeV1{testkit.Scope("unresolved.entry", "*")}, Methods: map[string]state.MethodPolicy{"/unresolved/Read": {Requires: []*basev0.WorkScopeV1{testkit.Scope("unresolved.entry", "read")}}}} if _, err := k.State.ApplyManifest(m); err != nil { diff --git a/modules/authority/services/local/code/pkg/business/state/restricted_method_test.go b/modules/authority/services/local/code/pkg/business/state/restricted_method_test.go index 4806b885..c82aa98e 100644 --- a/modules/authority/services/local/code/pkg/business/state/restricted_method_test.go +++ b/modules/authority/services/local/code/pkg/business/state/restricted_method_test.go @@ -12,7 +12,7 @@ import ( func TestMethodAdmissionNeverWidensResourceRestrictedScopes(t *testing.T) { k := testkit.New(t) - m := state.Manifest{Module: "gauge", Principal: "gauge", Kind: "module", Generation: 1, + m := state.Manifest{Name: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Methods: map[string]state.MethodPolicy{ "/gauge.Service/List": reads("gauge.items"), "/gauge.Service/Edit": {Requires: []*basev0.WorkScopeV1{scope("gauge.items", nil, "read", "write")}}, diff --git a/modules/authority/services/local/code/pkg/business/state/route_test.go b/modules/authority/services/local/code/pkg/business/state/route_test.go index ae708f25..1c91f423 100644 --- a/modules/authority/services/local/code/pkg/business/state/route_test.go +++ b/modules/authority/services/local/code/pkg/business/state/route_test.go @@ -21,7 +21,7 @@ func reads(kind string) state.MethodPolicy { // served, and two matching routes are refused rather than guessed between. func TestAnHTTPRequestIsAdmittedUnderTheRouteItsPathMatches(t *testing.T) { k := testkit.New(t) - m := state.Manifest{Module: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Bindings: map[string]state.Binding{}, + m := state.Manifest{Name: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Bindings: map[string]state.Binding{}, Methods: map[string]state.MethodPolicy{ "GET /v1/gauge/profiles": reads("gauge.profiles"), "GET /v1/gauge/profiles/{ref}": reads("gauge.profiles"), @@ -64,11 +64,11 @@ func TestAnHTTPRequestIsAdmittedUnderTheRouteItsPathMatches(t *testing.T) { // carries; a delivered module is not replaced by a stale one. func TestADeliveryReplacesTheFixturesStandIn(t *testing.T) { k := testkit.New(t) - standIn := state.Manifest{Module: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Bindings: map[string]state.Binding{}, StandIn: true} + standIn := state.Manifest{Name: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Bindings: map[string]state.Binding{}, StandIn: true} if _, err := k.State.ApplyManifest(standIn); err != nil { t.Fatal(err) } - delivered := state.Manifest{Module: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Bindings: map[string]state.Binding{}, + delivered := state.Manifest{Name: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Bindings: map[string]state.Binding{}, Methods: map[string]state.MethodPolicy{"GET /v1/gauge/profiles": reads("gauge.profiles")}} if _, err := k.State.ApplyManifest(delivered); err != nil { t.Fatalf("the module's own delivery did not replace the stand-in: %v", err) @@ -80,7 +80,7 @@ func TestADeliveryReplacesTheFixturesStandIn(t *testing.T) { func TestConcreteInventorySelectionPreservesManifestPolicy(t *testing.T) { k := testkit.New(t) - m := state.Manifest{Module: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Methods: map[string]state.MethodPolicy{ + m := state.Manifest{Name: "gauge", Principal: "gauge", Kind: "module", Generation: 1, Methods: map[string]state.MethodPolicy{ "GET /things/{id}": {AnyCheckedCaller: true}, "GET /things/export": {Requires: []*basev0.WorkScopeV1{{ResourceKind: "gauge.items", Actions: []string{"write"}}}}, "GET /things/{name}": {AnyCheckedCaller: true}, diff --git a/modules/authority/services/local/code/pkg/business/state/served_test.go b/modules/authority/services/local/code/pkg/business/state/served_test.go index 07832865..a1a4df96 100644 --- a/modules/authority/services/local/code/pkg/business/state/served_test.go +++ b/modules/authority/services/local/code/pkg/business/state/served_test.go @@ -15,14 +15,14 @@ import ( // manifest's other declarations named the kind. func TestAModuleServesWhatItsMethodsRequire(t *testing.T) { k := testkit.New(t) - m := state.Manifest{Module: "ledger", Principal: "ledger", Kind: "module", Generation: 1, Bindings: map[string]state.Binding{}, + m := state.Manifest{Name: "ledger", Principal: "ledger", Kind: "module", Generation: 1, Bindings: map[string]state.Binding{}, Methods: map[string]state.MethodPolicy{ "/ledger.v1.Ledger/DefineAccount": {Requires: []*basev0.WorkScopeV1{{ResourceKind: "ledger.accounts", Actions: []string{"define"}}}}, }} if _, err := k.State.ApplyManifest(m); err != nil { t.Fatal(err) } - held, err := k.State.PersonScopes("dev", "ledger") + held, err := k.PersonScopes(t, "dev", "ledger") if err != nil { t.Fatal(err) } diff --git a/modules/authority/services/local/code/pkg/business/state/solution_roles.go b/modules/authority/services/local/code/pkg/business/state/solution_roles.go deleted file mode 100644 index 9dea510c..00000000 --- a/modules/authority/services/local/code/pkg/business/state/solution_roles.go +++ /dev/null @@ -1,48 +0,0 @@ -package state - -import ( - "fmt" - basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" - "slices" -) - -// PersonSolutionScopes returns the person's full effective installation roles, -// including declared consumed permissions. It never reads organization roles. -// Issuers fence this read with their existing authorization revision checks. -func (s *State) PersonSolutionScopes(person, solution string) ([]*basev0.WorkScopeV1, error) { - s.mu.RLock() - defer s.mu.RUnlock() - p, ok := s.people[person] - if !ok { - return nil, fmt.Errorf("%w: person", ErrUnknown) - } - m, err := s.moduleByPrincipal(solution) - if err != nil { - return nil, err - } - if m.Kind != "solution" { - return nil, fmt.Errorf("%w: %s is not a solution", ErrForbidden, solution) - } - return s.personSolutionScopes(p, m), nil -} - -// personSolutionScopes uses the caller's state lock. Owners hold installer; -// granted team members hold the selected role; both sources may contribute. -func (s *State) personSolutionScopes(p Person, m Manifest) []*basev0.WorkScopeV1 { - var held []*basev0.WorkScopeV1 - for _, inst := range s.entitled(p) { - if inst.Target != m.Module { - continue - } - if inst.Owner == p.ID { - held = append(held, m.Roles[InstallerRole]...) - } - for _, team := range inst.Teams { - if slices.Contains(p.Teams, team) { - held = append(held, m.Roles[inst.Role]...) - break - } - } - } - return Normalize(held) -} diff --git a/modules/authority/services/local/code/pkg/business/state/state.go b/modules/authority/services/local/code/pkg/business/state/state.go index dfefef91..2a130b5e 100644 --- a/modules/authority/services/local/code/pkg/business/state/state.go +++ b/modules/authority/services/local/code/pkg/business/state/state.go @@ -1,34 +1,36 @@ -// Package state is the local authority's whole memory: the fixture people and -// roles, the composition as delivered by manifests, the installations and the -// open sessions. It is the seal and revision source core's issuer and verifier -// read, so one struct answers every "is this still true" the authority asks. +// Package state is the local authority's host half: the fixture it boots on +// (organisations, roles, people and their sign-in tokens), the composition as +// delivered, the open sessions, delivery admission and method admission. What +// the authority decides and records (modules, installations, role grants, +// delegations, revisions, and every issuance) is the generic authority core's, +// over its in-memory store: this package seeds that store and asks the core. package state import ( "context" - "crypto/rand" - "encoding/hex" + "crypto/ed25519" "errors" "fmt" - delivery "github.com/codefly-dev/authority/libraries/delivery/go" - "github.com/codefly-dev/authority/libraries/hop/go/routing" - resources "github.com/codefly-dev/authority/libraries/resources/go" "slices" - "sort" "strings" "sync" + "github.com/codefly-dev/authority/libraries/authority-core/go/authority" + "github.com/codefly-dev/authority/libraries/authority-core/go/domain" + "github.com/codefly-dev/authority/libraries/authority-core/go/memstore" + "github.com/codefly-dev/authority/libraries/authority-core/go/scope" + "github.com/codefly-dev/authority/libraries/hop/go/routing" + resources "github.com/codefly-dev/authority/libraries/resources/go" basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" - "github.com/codefly-dev/core/workcontext" "github.com/codefly-dev/core/workloadidentity/native" "github.com/google/uuid" "gopkg.in/yaml.v3" ) -// Errors a caller distinguishes. +// Errors a caller distinguishes for what this host decides itself; what the +// core decides arrives as a domain.Refusal with its code. var ( ErrUnknown = errors.New("state: unknown") - ErrNotInstalled = errors.New("state: not installed") ErrUnauthenticated = errors.New("state: unauthenticated") ErrForbidden = errors.New("state: forbidden") ErrStale = errors.New("state: generation is not newer") @@ -76,15 +78,43 @@ type Role struct { Scopes []Scope `yaml:"scopes"` } -// Binding is a module's declared call towards another module. -type Binding struct { - ID string - Audience string - InvokeScopes []*basev0.WorkScopeV1 - HeadlessScopes []*basev0.WorkScopeV1 - // LookupScopes are what a receipt lookup through the binding may read. - LookupScopes []*basev0.WorkScopeV1 -} +// The core's objects, as this host names them. +type ( + Manifest = domain.Module + MethodPolicy = domain.MethodPolicy + Binding = domain.Binding + Installation = domain.Installation + Status = domain.Status + Delegation = domain.Delegation + InstallStep = authority.InstallStep +) + +// The installation states, the reserved role and the wildcards. +const ( + Pending = domain.Pending + Active = domain.Active + Revoked = domain.Revoked + InstallerRole = domain.InstallerRole + AnyAction = scope.AnyAction + AnyKind = scope.AnyKind +) + +// The core's scope algebra, for this host's own checks. +var ( + Normalize = scope.Normalize + Intersect = scope.Intersect + Attenuates = scope.Attenuates + HoldsAction = scope.HoldsAction +) + +// InstallationID is the id of org's installation of target: a canonical UUID +// named by organisation and target, so a module's stored rows still name the +// installation after the authority restarts with its fixture. +func InstallationID(org, target string) string { return authority.StableInstallationID(org, target) } + +// Namespace is the source an installation's authored content belongs to, +// named by the host so a module never chooses it: ".". +func Namespace(i Installation) string { return i.Organization + "." + i.Target } // DelegationScope preserves the exact resource ceiling of a WorkScope. // Role matchers are a separate contract; a delegation names IDs, not patterns. @@ -94,8 +124,8 @@ type DelegationScope struct { ResourceIDs []string `yaml:"resource_ids,omitempty"` } -// Refuse role-matcher syntax instead of silently decoding a restricted grant -// as an unrestricted one. Fixture decoding otherwise permits unknown fields. +// UnmarshalYAML refuses role-matcher syntax instead of silently decoding a +// restricted grant as an unrestricted one. func (s *DelegationScope) UnmarshalYAML(node *yaml.Node) error { type plain DelegationScope var decoded struct { @@ -112,234 +142,16 @@ func (s *DelegationScope) UnmarshalYAML(node *yaml.Node) error { return nil } -// Delegation is a person's grant to a module to act for them beyond any -// session, on scopes they hold. Every capability issued from it is the -// grantor's, re-checked against what the grantor holds at the time. -type Delegation struct { +// fixtureDelegation is a delegation a fixture person has already granted. +type fixtureDelegation struct { ID string `yaml:"id"` Organization string `yaml:"organization"` Grantor string `yaml:"grantor"` Module string `yaml:"module"` Purpose string `yaml:"purpose"` Scopes []DelegationScope `yaml:"scopes"` - Revoked bool `yaml:"-"` -} - -// WorkScopes is the delegation's scopes as core spells them. -func (d Delegation) WorkScopes() []*basev0.WorkScopeV1 { - var out []*basev0.WorkScopeV1 - for _, sc := range d.Scopes { - out = append(out, &basev0.WorkScopeV1{ - ResourceKind: sc.Kind, Actions: append([]string(nil), sc.Actions...), - ResourceIds: append([]string(nil), sc.ResourceIDs...), - }) - } - return Normalize(out) -} - -// Manifest is what the render delivered for one module instance. -type Manifest struct { - Module string - Principal string - Kind string - Generation uint64 - // StandIn marks a module the fixture declares for a run that supervises it - // itself: what it serves, until the module's own delivery replaces it. - StandIn bool - Bindings map[string]Binding - ResourceKinds []*basev0.WorkScopeV1 - // Roles are what a solution declares a person may hold on an installation of - // it, each within what the solution serves or explicitly consumes through - // invoke/lookup bindings. A solution always declares - // InstallerRole; a module declares none, since a module is installed by - // composition and a person's access to it is the organisation's roles. - Roles map[string][]*basev0.WorkScopeV1 - // Methods are what each method the module serves declares a call must hold, - // from the authority's signed document delivered with the manifest. Nil - // means the delivery declared no method policy: every check naming one of - // the module's methods is then refused. - Methods map[string]MethodPolicy -} - -// MethodPolicy is one method's declaration: exactly one of its fields is set. -type MethodPolicy struct { - Requires []*basev0.WorkScopeV1 - AnyCheckedCaller bool -} - -// servedByMethods is every (kind, action) a module's methods require of a call: -// what the module serves, read from its own method declarations rather than -// from any summary of them. A person's or an installation's grant at the module -// is cut to what it serves, so a kind a method requires is never one nobody can -// be granted. -func servedByMethods(methods map[string]MethodPolicy) []*basev0.WorkScopeV1 { - var served []*basev0.WorkScopeV1 - for _, policy := range methods { - for _, required := range policy.Requires { - served = append(served, &basev0.WorkScopeV1{ResourceKind: required.GetResourceKind(), Actions: append([]string(nil), required.GetActions()...)}) - } - } - return served -} - -// AdmitMethod applies what the module at audience declares for method to the -// scopes a checked call holds. A method that declares nothing, or both that it -// requires something and nothing, is refused: an absent or ambiguous -// declaration is never read as permission. -func (s *State) AdmitMethod(audience, method string, held []*basev0.WorkScopeV1) error { - s.mu.RLock() - m, err := s.moduleByPrincipal(audience) - s.mu.RUnlock() - if err != nil { - return err - } - return admitMethod(m, audience, method, held) -} - -func admitMethod(m Manifest, audience, method string, held []*basev0.WorkScopeV1) error { - var err error - if m.Methods == nil { - return fmt.Errorf("%w: %s's delivery declares no method policy, so %s is refused", ErrForbidden, audience, method) - } - p, ok := m.Methods[method] - if !ok { - if p, ok, err = routeOf(m.Methods, method); err != nil { - return fmt.Errorf("%w: %s: %w", ErrForbidden, audience, err) - } - } - switch { - case !ok: - return fmt.Errorf("%w: %s serves no method %s", ErrUnknown, audience, method) - case p.AnyCheckedCaller && len(p.Requires) > 0: - return fmt.Errorf("%w: %s declares for %s both that it requires scopes and that it requires none", ErrForbidden, audience, method) - case p.AnyCheckedCaller: - return nil - case len(p.Requires) == 0: - return fmt.Errorf("%w: %s declares no policy for %s", ErrForbidden, audience, method) - } - // Method admission checks the kind and action. The module must still check - // each requested resource against the unchanged resource-limited scopes. - var missing []string - for _, r := range p.Requires { - for _, action := range r.GetActions() { - if !holdsAction(held, r.GetResourceKind(), action) { - missing = append(missing, r.GetResourceKind()+":"+action) - } - } - } - if len(missing) > 0 { - return fmt.Errorf("%w: %s requires %s for %s, which the call does not hold (it holds %s)", - ErrForbidden, audience, strings.Join(missing, ", "), method, describe(held)) - } - return nil -} - -// routeOf is the route a module declares that an HTTP request " " -// is for: a declared "