From d8cc00b54cc6a148021e051362b212b7750d846c Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Fri, 8 May 2020 10:49:23 +0300 Subject: [PATCH] Update to neofs-api v0.7.4 --- Makefile | 2 +- docs/service.md | 16 ++++++++++++++-- docs/session.md | 25 ++++++++---------------- service/verify.pb.go | Bin 35203 -> 39615 bytes service/verify.proto | 18 ++++++++++++------ session/service.go | 10 ---------- session/service.pb.go | Bin 27455 -> 21636 bytes session/service.proto | 43 +++++++++++++++--------------------------- 8 files changed, 50 insertions(+), 64 deletions(-) diff --git a/Makefile b/Makefile index 62a92ec7..b99682bf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PROTO_VERSION=v0.7.3 +PROTO_VERSION=v0.7.4 PROTO_URL=https://github.com/nspcc-dev/neofs-api/archive/$(PROTO_VERSION).tar.gz B=\033[0;1m diff --git a/docs/service.md b/docs/service.md index eef1e49f..9ed548ef 100644 --- a/docs/service.md +++ b/docs/service.md @@ -17,6 +17,7 @@ - [RequestVerificationHeader.Signature](#service.RequestVerificationHeader.Signature) - [Token](#service.Token) - [Token.Info](#service.Token.Info) + - [TokenLifetime](#service.TokenLifetime) - [service/verify_test.proto](#service/verify_test.proto) @@ -129,10 +130,21 @@ User token granting rights for object manipulation | OwnerID | [bytes](#bytes) | | OwnerID is an owner of manipulation object | | verb | [Token.Info.Verb](#service.Token.Info.Verb) | | Verb is a type of request for which the token is issued | | Address | [refs.Address](#refs.Address) | | Address is an object address for which token is issued | -| Created | [uint64](#uint64) | | Created is an initial epoch of token lifetime | -| ValidUntil | [uint64](#uint64) | | ValidUntil is a last epoch of token lifetime | +| Lifetime | [TokenLifetime](#service.TokenLifetime) | | Lifetime is a lifetime of the session | | SessionKey | [bytes](#bytes) | | SessionKey is a public key of session key | + + + +### Message TokenLifetime +TokenLifetime carries a group of lifetime parameters of the token + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Created | [uint64](#uint64) | | Created carries an initial epoch of token lifetime | +| ValidUntil | [uint64](#uint64) | | ValidUntil carries a last epoch of token lifetime | + diff --git a/docs/session.md b/docs/session.md index 4a537e65..5ec74025 100644 --- a/docs/session.md +++ b/docs/session.md @@ -30,22 +30,13 @@ ``` -rpc Create(stream CreateRequest) returns (stream CreateResponse); +rpc Create(CreateRequest) returns (CreateResponse); ``` #### Method Create -Create is a method that used to open a trusted session to manipulate -an object. In order to put or delete object client have to obtain session -token with trusted node. Trusted node will modify client's object -(add missing headers, checksums, homomorphic hash) and sign id with -session key. Session is established during 4-step handshake in one gRPC stream - -- First client stream message SHOULD BE type of `CreateRequest_Init`. -- First server stream message SHOULD BE type of `CreateResponse_Unsigned`. -- Second client stream message SHOULD BE type of `CreateRequest_Signed`. -- Second server stream message SHOULD BE type of `CreateResponse_Result`. +Create opens new session between the client and the server | Name | Input | Output | | ---- | ----- | ------ | @@ -56,13 +47,13 @@ session key. Session is established during 4-step handshake in one gRPC stream ### Message CreateRequest - +CreateRequest carries an information necessary for opening a session | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| Init | [service.Token](#service.Token) | | Init is a message to initialize session opening. Carry: owner of manipulation object; ID of manipulation object; token lifetime bounds. | -| Signed | [service.Token](#service.Token) | | Signed Init message response (Unsigned) from server with user private key | +| OwnerID | [bytes](#bytes) | | OwnerID carries an identifier of a session initiator | +| Lifetime | [service.TokenLifetime](#service.TokenLifetime) | | Lifetime carries a lifetime of the session | | Meta | [service.RequestMetaHeader](#service.RequestMetaHeader) | | RequestMetaHeader contains information about request meta headers (should be embedded into message) | | Verify | [service.RequestVerificationHeader](#service.RequestVerificationHeader) | | RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) | @@ -70,13 +61,13 @@ session key. Session is established during 4-step handshake in one gRPC stream ### Message CreateResponse - +CreateResponse carries an information about the opened session | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| Unsigned | [service.Token](#service.Token) | | Unsigned token with token ID and session public key generated on server side | -| Result | [service.Token](#service.Token) | | Result is a resulting token which can be used for object placing through an trusted intermediary | +| ID | [bytes](#bytes) | | ID carries an identifier of session token | +| SessionKey | [bytes](#bytes) | | SessionKey carries a session public key | diff --git a/service/verify.pb.go b/service/verify.pb.go index 023e6392bb2ee316a7b3ddcc8c6726604981a694..3dadf0b559b9930f8b0dccd14d645b45c1b8f4d7 100644 GIT binary patch delta 4300 zcmZ{nO^6(28pr7|#+_D|fJThLnc9p-jjidfs;;gwNXAW8P!n-kML`d#Pi>v)8GCwS zYc{FX1yL6kHZRB?_99*cKM)#kipqkAJ*?*DLEQD~&8vd=zW-m(^o(L3UaH=DKL5}E zdEU4GYUV!vVDA0Rh5A&#Do6d*((7+{skgHAwpMwgr%G?5+}P-^4Vv>0uRT)^wq?(( z57$O(*=BXuq3rolxzXyj*49U@LAqMry(QF=OwcM_$PFI{|34%ar8LEcZQBE{Kj@G8>56%6;aH<%K3h1kmyf2oc*kLH z`?uL2jH1xK?m*#eI<&hj-~4g$n7Od;)vI?UVE%Uf!qOHl=gxcQo7LtZ_ikPFj_j8E z@cNU#*^;|>U}^i=9J%tLgZ}yEuPVNA<;kD9{QSU&S4>TrAFm0_zNO1&=5AP8oV|Q= zLpXbRZt3W@+R^>D?^gTY(vg|uxtkuFy?lAoBa@3azu7SVK74St`}N`LW-tCNug^t2 z^PgK*>u%8U+T*ZrYgD{`?HxXk27wlh5^T2p0G$#Vxaq99vA6$b(-)K;vm0H_p_zXOJjo-x3K&id-HFxz2J436elLl=2x#xdaS7S0n`tn3gX4V4*bYaY_z^QK~?R zGy=fTb12(w?Q~@|R}@&vwBsxDKo)FqZ8~7lE0jWjA%fAG*}~M8T+%M29V>N8QVEPg zesa*0TCSCxedTYVa$HU1x&%F`!GmB&@Kd78MB6&H{%sL$tc8RsnW)4V5N@Mf$wLRL zDn_EPQz@VvsU)#v=!r@@z>q6Jk#5{mF=CsP_1~4b7_ugE*V)0eSh77?g+x7-f!yfH zuA&GelM1S?GAyMVQTiSr39(EdB}uEcsb|uI8eM%(6f51(5g3^~B(jt!x`420E=8Rt z@=(b(l|+rEjPNN#43Sc`l83fLX;4QZTRp8^DY&v)RM}Br zfy@C61qdW#8Se7W<-tx1`)q6Cs08;zk`|zm?B(Z4&DwDe za&n7n=d=x!Dj6h>WDok#Bg`WNVH6()MFY~fiYvMTDED20GddYk;+8z6{F8|Q51|!O9NMiLI7l> zD`lo7v&R`BoJ0P3~h5}=P+057}6fnmxhbHXpC{GKq z^+`#9hvJ2DhRx9WcOELZuK>6nqPXG zEZin)J9llAoLbO?^MyG90l_U*w$wADNQ%yu+j-yy6KN~i0Sts@1pvkrg{dvX)<0?t zc7$jpd`>-(BE7_T%T@YN9D>)3MkY5vV2bDPZ3ucB2 z(-qv14tOXGqXY=KsTJ~UzdDehHRd!?>gnxqdU=AZDj$<8QAA`;(Wh4qRk9<=SkIOo z&I90bT>8S6{@_;If#Pt>6{#MBAq|=;tLc3~$~}paRTnB%VhqOx3``X^T#XeHM{y<^ z00uLgo}?Re38=?*Tss2DgzZC@1HHrzhuaAk26~1wfFlxq{8Exw82bQLsSNf=l>-Il z=w|O9*qa{kl0vq}DW?K(Tuo7xa;RBCiBoB%Tk_5W^OFEV;1+vm90r5~&-&-y$n*zK z<|Ox7dYK%!NLgY!58TD3LrLR+$u%8B^2P{=s4A%`7W}tuAiN`Z}|D;(q&|_LCF&p13)Ge)8$PH}1V=#k<>Ey>s_J(Br3Cb1R;?^!xqh-07Fi zyARB7{~dh#zLVzbQ!kt7{y$9`4}RY=?F-Ah|8!2gNA}E_eP`#*nMZg0+w{(!X+-A5 zv)4}A7e2Lr@dvZ`C;xr$|Bb75hG5=){E6LG%|F(w$qz5Q*f5_xyx;ur!t&(Opa0O9 O6f0jfCf~huaqmwCMVc=F delta 3378 zcmYk8y~`a%6vnwPN#sqTm53to1(U+?zOcJ@KkmjR5-tk1eEGu3{P6tKt8$(%yg2`N?%h?jyZPMrr{+IixISOI`q+H= z;`RCD;vw_DpMQP#^~L+E`P1Y1AW!EfPTrsD?D#0z=6xJDLmc}xE8X$g!(^K#Y?N_q zsyL<`&6wvHlucZ(n6DYDF|H(>Oj@jPnx%2B?WTzU)3(^ewK8Xiu{0#@>bRaTYTe?a z2Gi&|R&qTUFFtz^wmHx#DGXh7mol~yVA>9#8AXDRirfZ#RG4izQ!%Ev>&UZ9AZbl_ zy6Euj(Hi4$C?;)Ei~|8DF@v6I4Ooq|PWd_-((+TClAM=Dm^5>Rhh*Q|R>Y{q1? z&;(Fn&|%QysN5?+X}~@BJ#niX238y7UeQxx3=GFYS6H{0DLV}#mZ4@z8hS`nY6@_cUcsMa*xV91Kup2d5UH)=nS7t5rkrAw9cbaI zhk1=f#k?mq$Y4X&dluH1lks-U6sAwm}UG zCHRF13d;!5DQ2O^Vn11K5|Mguy#-6?{StSZHTc$0WYs0;KrD2NCneQ%?Z%c~wV?*d z1K%1-w6D*|$&oY4{)Wcvm5g*UdXP52*!-UzItCWyW zg1g+fly2o^&$~)N$XX8S7aM7a}Xrbcgj1N5q8VS z$75ST=VbL#5f+J}Ko&9Ml(*Zv)bZd%lpN^&)~~*uIvVf8MtDzR^iG1DQ|T65gy>qZ&}wn$cgF z(ip~wp!M=g#z0ZBW0%hxX#U!2G*g9MzL1C&pUsdz+dePh;}H?O#mlEtk@1N%B&;p5 z&!zt*Ux*BMiq(jf&;JOWL98RdqFI6Gn}IQMSp3nlM!AM7U!s;M`~>*tTU^i{%U2aY zK{z8qQq6B)diEJLLj>ETC%!V-eS7KtC?%x}E< z)b7t~zn`8T-S}!gyz#^Cx3?Z0&kyci-hJ}TORM?bohNq>?tZyC(={IsN7ek^J8$m( Oy7T#|-O0W0&ioJOLuSVS diff --git a/service/verify.proto b/service/verify.proto index b25cd479..ed360beb 100644 --- a/service/verify.proto +++ b/service/verify.proto @@ -58,14 +58,11 @@ message Token { // Address is an object address for which token is issued refs.Address Address = 4 [(gogoproto.nullable) = false, (gogoproto.customtype) = "Address"]; - // Created is an initial epoch of token lifetime - uint64 Created = 5; - - // ValidUntil is a last epoch of token lifetime - uint64 ValidUntil = 6; + // Lifetime is a lifetime of the session + TokenLifetime Lifetime = 5 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; // SessionKey is a public key of session key - bytes SessionKey = 7; + bytes SessionKey = 6; } // TokenInfo is a grouped information about token @@ -75,6 +72,15 @@ message Token { bytes Signature = 8; } +// TokenLifetime carries a group of lifetime parameters of the token +message TokenLifetime { + // Created carries an initial epoch of token lifetime + uint64 Created = 1; + + // ValidUntil carries a last epoch of token lifetime + uint64 ValidUntil = 2; +} + // TODO: for variable token types and version redefine message // Example: // message Token { diff --git a/session/service.go b/session/service.go index 6e293d36..ab876163 100644 --- a/session/service.go +++ b/session/service.go @@ -1,11 +1 @@ package session - -// NewInitRequest returns new initialization CreateRequest from passed Token. -func NewInitRequest(t *Token) *CreateRequest { - return &CreateRequest{Message: &CreateRequest_Init{Init: t}} -} - -// NewSignedRequest returns new signed CreateRequest from passed Token. -func NewSignedRequest(t *Token) *CreateRequest { - return &CreateRequest{Message: &CreateRequest_Signed{Signed: t}} -} diff --git a/session/service.pb.go b/session/service.pb.go index 1088308c46f56a8bbc670655cdb94f03a1fae586..e68c0fdd58e180f56fc7c5b5de292b9c1da029a1 100644 GIT binary patch delta 4753 zcmbVPO^jPt71ktahz)J?+ey+kdT~l)CyvLz&-Rdsm}Vw0Fm0nuQ>2apc|Z3hkJ!)7 z^P9v&6s$_H2i*liP!LOY6<)HSNZBA3u<8mSSiuIdM<6)&`<`Q)X)8*k+_}E@ob!F> z=bm@(uRdJ(&d(R#-`W4Ar6ucXteiyMQ1^G#AhA3rjzcxDoX85Jpci+YB@KvfIbbwgJys-yHO!j(b$%<4>p2mM|&m?1GAV#n?6B;aDgtNaNi{oUtsjZX?|ngc5_jI$XL z1^10L{bBaZXl+7wCR*wKgs+5+aoS=wzp8d;A~iAJ-*G#kXWdh~+02Zf_4MENEt4?$ zIh#WLhvSDYO~q3(6XcnVUN=lsx1a3lx1Z1H*1@wnICw$7cQCK57Y>cj9gg;ET{|K_ zKKtTSD_^iu)JNG&7+78X?&V{j>z1Yx`kyCGj?bOUFD%%4^VFS@T`yYYVY^~RFEFF6 z%;-C2te1<~OnF!-W7{@kqisgVHDkGIMi(ZW2G)J7Kv`j|z^F(0rrE7v-UQ^RRRuz; zW_E07w}8+9jRtHuSZPDM0ZigM*!6*|z^)B6T4+MjYXQK6=^BJ=f*Et8VTIaY)5P5M z43{!kRd>ctAA;b<#jA%e^V7&$$7MH@h zjj;koHOSW>*>-{8!giI0pv;g|F(=g;XxC*$@Oc85874h|GKD%b7sSZ zZKk^cN95p`;9+dQ=NeOjl{B;x9>HhK6-+1=YYjf|On7jY1e)OOlRXj|pgufs5oN*j zSd$DDiyo2+GJX>-G!VU(UmS@BaROl05lZ^Ta0d`=@&u3;W7BE2^Mz+yj_k!V*%Kh^BmT^86RxKjd?4BK!Ym`4>H-J>*Ru(WU9(7 z;0F+`BYIVO=mWt4Z#JAV<|L+Im7#AznK@)T^squqwy)GE0C@J&TD6-ald>A!MJOxa zYD#m`4i$JaxXfD0%j60fm6W6A-|VkTLGCCNWA5WyMkOpIwPvhlJD9&2IyVRdI@Z zW(hcT*ro!zWn<=Kq%{n}HrTN#v)}PUP;*D{A^rS;QxnOhyEf1iaCktO9(HSKY2pJL zz8oc)^9#jjr(dzO?Xf0cng_9tTvbLh@nNH`!YaEMxv*RDtI3wm6U$7>c{gdGiwtWE zN#|Ft$K*jp<`7?*Z$ht853eaCW|FZr`;iUfl`6nN@ zx%iGHS1xm7U0yMe`H2Buatqmviet0ql{~Ms5j~L0Y2HhQMJu;7;5Bn{OIxC9t|-rf zq9rEv-}2RLtdgBL!p|cY?`{7dzGX8socDTBqJ~N7D*qPey-oCw3NPyQ(paybKXEix z_xEm}e%?5nGK#`ZQJ-De*cQCe}6gii_44qu6I+k0M z)ns)$T)tHlR?}xuA*+AbzHn@hR-vHZdF`d9F*|i;W9@(PLmr$T<-#lDAG~HSywtHi zDlUlgH& zuWgNg`Q;Dxe$=KeI=Ge*gdg literal 27455 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