github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/cursor/cursor.go (about) 1 package cursor 2 3 import ( 4 "encoding/base64" 5 "errors" 6 "fmt" 7 8 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 9 10 "github.com/authzed/spicedb/pkg/datastore" 11 dispatch "github.com/authzed/spicedb/pkg/proto/dispatch/v1" 12 impl "github.com/authzed/spicedb/pkg/proto/impl/v1" 13 "github.com/authzed/spicedb/pkg/spiceerrors" 14 ) 15 16 // Encode converts a decoded cursor to its opaque version. 17 func Encode(decoded *impl.DecodedCursor) (*v1.Cursor, error) { 18 marshalled, err := decoded.MarshalVT() 19 if err != nil { 20 return nil, NewInvalidCursorErr(fmt.Errorf(errEncodeError, err)) 21 } 22 23 return &v1.Cursor{ 24 Token: base64.StdEncoding.EncodeToString(marshalled), 25 }, nil 26 } 27 28 // Decode converts an encoded cursor to its decoded version. 29 func Decode(encoded *v1.Cursor) (*impl.DecodedCursor, error) { 30 if encoded == nil { 31 return nil, NewInvalidCursorErr(errors.New("cursor pointer was nil")) 32 } 33 34 decodedBytes, err := base64.StdEncoding.DecodeString(encoded.Token) 35 if err != nil { 36 return nil, NewInvalidCursorErr(fmt.Errorf(errDecodeError, err)) 37 } 38 39 decoded := &impl.DecodedCursor{} 40 if err := decoded.UnmarshalVT(decodedBytes); err != nil { 41 return nil, NewInvalidCursorErr(fmt.Errorf(errDecodeError, err)) 42 } 43 44 return decoded, nil 45 } 46 47 // EncodeFromDispatchCursor encodes an internal dispatching cursor into a V1 cursor for external 48 // consumption, including the provided call context to ensure the API cursor reflects the calling 49 // API method. The call hash should contain all the parameters of the calling API function, 50 // as well as its revision and name. 51 func EncodeFromDispatchCursor(dispatchCursor *dispatch.Cursor, callAndParameterHash string, revision datastore.Revision) (*v1.Cursor, error) { 52 if dispatchCursor == nil { 53 return nil, spiceerrors.MustBugf("got nil dispatch cursor") 54 } 55 56 return Encode(&impl.DecodedCursor{ 57 VersionOneof: &impl.DecodedCursor_V1{ 58 V1: &impl.V1Cursor{ 59 Revision: revision.String(), 60 DispatchVersion: dispatchCursor.DispatchVersion, 61 Sections: dispatchCursor.Sections, 62 CallAndParametersHash: callAndParameterHash, 63 }, 64 }, 65 }) 66 } 67 68 // DecodeToDispatchCursor decodes an encoded API cursor into an internal dispatching cursor, 69 // ensuring that the provided call context matches that encoded into the API cursor. The call 70 // hash should contain all the parameters of the calling API function, as well as its revision 71 // and name. 72 func DecodeToDispatchCursor(encoded *v1.Cursor, callAndParameterHash string) (*dispatch.Cursor, error) { 73 decoded, err := Decode(encoded) 74 if err != nil { 75 return nil, err 76 } 77 78 v1decoded := decoded.GetV1() 79 if v1decoded == nil { 80 return nil, NewInvalidCursorErr(ErrNilCursor) 81 } 82 83 if v1decoded.CallAndParametersHash != callAndParameterHash { 84 return nil, NewInvalidCursorErr(ErrHashMismatch) 85 } 86 87 return &dispatch.Cursor{ 88 DispatchVersion: v1decoded.DispatchVersion, 89 Sections: v1decoded.Sections, 90 }, nil 91 } 92 93 // DecodeToDispatchRevision decodes an encoded API cursor into an internal dispatch revision. 94 // NOTE: this method does *not* verify the caller's method signature. 95 func DecodeToDispatchRevision(encoded *v1.Cursor, ds revisionDecoder) (datastore.Revision, error) { 96 decoded, err := Decode(encoded) 97 if err != nil { 98 return nil, err 99 } 100 101 v1decoded := decoded.GetV1() 102 if v1decoded == nil { 103 return nil, ErrNilCursor 104 } 105 106 parsed, err := ds.RevisionFromString(v1decoded.Revision) 107 if err != nil { 108 return datastore.NoRevision, fmt.Errorf(errDecodeError, err) 109 } 110 111 return parsed, nil 112 } 113 114 type revisionDecoder interface { 115 RevisionFromString(string) (datastore.Revision, error) 116 }