diff --git a/cmd/neofs-rest-gw/integration_test.go b/cmd/neofs-rest-gw/integration_test.go index de5e053..0dc6c6b 100644 --- a/cmd/neofs-rest-gw/integration_test.go +++ b/cmd/neofs-rest-gw/integration_test.go @@ -25,6 +25,7 @@ import ( "github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations" "github.com/nspcc-dev/neofs-rest-gw/handlers" "github.com/nspcc-dev/neofs-rest-gw/internal/util" + "github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/container" "github.com/nspcc-dev/neofs-sdk-go/container/acl" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" @@ -117,6 +118,7 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s t.Run("rest auth several tokens "+version, func(t *testing.T) { authTokens(ctx, t) }) t.Run("rest check mix tokens up "+version, func(t *testing.T) { mixTokens(ctx, t, cnrID) }) + t.Run("rest form full binary bearer "+version, func(t *testing.T) { formFullBinaryBearer(ctx, t) }) t.Run("rest put object "+version, func(t *testing.T) { restObjectPut(ctx, t, clientPool, cnrID) }) t.Run("rest get object "+version, func(t *testing.T) { restObjectGet(ctx, t, clientPool, &owner, cnrID) }) @@ -319,6 +321,68 @@ func mixTokens(ctx context.Context, t *testing.T, cnrID cid.ID) { checkPutObjectWithError(t, httpClient, cnrID, containerSetEACLToken) } +func formFullBinaryBearer(ctx context.Context, t *testing.T) { + bearers := []*models.Bearer{ + { + Name: "all-object", + Object: []*models.Record{{ + Operation: models.NewOperation(models.OperationPUT), + Action: models.NewAction(models.ActionALLOW), + Filters: []*models.Filter{}, + Targets: []*models.Target{{ + Role: models.NewRole(models.RoleOTHERS), + Keys: []string{}, + }}, + }}, + }, + { + Name: "put-container", + Container: &models.Rule{ + Verb: models.NewVerb(models.VerbPUT), + }, + }, + } + + httpClient := defaultHTTPClient() + tokens := makeAuthTokenRequest(ctx, t, bearers, httpClient) + objectToken := tokens[0] + containerPutToken := tokens[1] + + query := make(url.Values) + query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) + + // check that container token isn't valid + request, err := http.NewRequest(http.MethodGet, testHost+"/v1/auth/bearer?"+query.Encode(), nil) + require.NoError(t, err) + prepareCommonHeaders(request.Header, containerPutToken) + checkGWErrorResponse(t, httpClient, request) + + // check that object bearer token is valid + request, err = http.NewRequest(http.MethodGet, testHost+"/v1/auth/bearer?"+query.Encode(), nil) + require.NoError(t, err) + prepareCommonHeaders(request.Header, objectToken) + resp := &models.BinaryBearer{} + doRequest(t, httpClient, request, http.StatusOK, resp) + + actualTokenRaw, err := base64.StdEncoding.DecodeString(*resp.Token) + require.NoError(t, err) + + var actualToken bearer.Token + err = actualToken.Unmarshal(actualTokenRaw) + require.NoError(t, err) + + require.True(t, actualToken.VerifySignature()) + require.Len(t, actualToken.EACLTable().Records(), 1) + actualRecord := actualToken.EACLTable().Records()[0] + require.Equal(t, eacl.OperationPut, actualRecord.Operation()) + require.Equal(t, eacl.ActionAllow, actualRecord.Action()) + require.Empty(t, actualRecord.Filters()) + require.Len(t, actualRecord.Targets(), 1) + actualTarget := actualRecord.Targets()[0] + require.Empty(t, actualTarget.BinaryKeys()) + require.Equal(t, eacl.RoleOthers, actualTarget.Role()) +} + func checkPutContainerWithError(t *testing.T, httpClient *http.Client, token *handlers.BearerToken) { reqURL, err := url.Parse(testHost + "/v1/containers") require.NoError(t, err) diff --git a/gen/models/binary_bearer.go b/gen/models/binary_bearer.go new file mode 100644 index 0000000..44cbd24 --- /dev/null +++ b/gen/models/binary_bearer.go @@ -0,0 +1,72 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// BinaryBearer Bearer token for object operations that is represented in binary form. +// Example: {"token":"ChIKDAoAGggIARABIgIIAxoCCGQSZgohA+J5jFWFMiOpyvMZBu9wwPTKsWsG0q206kVe63iuWP/wEkEE4SIV0QngnKppDf54QezUKmar7UQby6HzufT5yVIOvj7QEqZnOavrKW0chCeCwP0khda/j9k00ct6NMEDxQFW+g=="} +// +// swagger:model BinaryBearer +type BinaryBearer struct { + + // Base64 encoded bearer token. + // Required: true + Token *string `json:"token"` +} + +// Validate validates this binary bearer +func (m *BinaryBearer) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateToken(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *BinaryBearer) validateToken(formats strfmt.Registry) error { + + if err := validate.Required("token", "body", m.Token); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this binary bearer based on context it is used +func (m *BinaryBearer) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *BinaryBearer) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *BinaryBearer) UnmarshalBinary(b []byte) error { + var res BinaryBearer + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/gen/restapi/embedded_spec.go b/gen/restapi/embedded_spec.go index 687b081..c930b7a 100644 --- a/gen/restapi/embedded_spec.go +++ b/gen/restapi/embedded_spec.go @@ -110,7 +110,7 @@ func init() { ], "responses": { "200": { - "description": "Base64 encoded stable binary marshaled bearer token.", + "description": "Base64 encoded stable binary marshaled bearer token bodies.", "schema": { "type": "array", "items": { @@ -149,6 +149,45 @@ func init() { } } }, + "/auth/bearer": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Form binary bearer token", + "operationId": "formBinaryBearer", + "parameters": [ + { + "$ref": "#/parameters/signatureParam" + }, + { + "$ref": "#/parameters/signatureKeyParam" + }, + { + "$ref": "#/parameters/signatureScheme" + } + ], + "responses": { + "200": { + "description": "Base64 encoded stable binary marshaled bearer token.", + "schema": { + "$ref": "#/definitions/BinaryBearer" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, "/containers": { "get": { "security": [], @@ -844,6 +883,22 @@ func init() { } ] }, + "BinaryBearer": { + "description": "Bearer token for object operations that is represented in binary form.", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "description": "Base64 encoded bearer token.", + "type": "string" + } + }, + "example": { + "token": "ChIKDAoAGggIARABIgIIAxoCCGQSZgohA+J5jFWFMiOpyvMZBu9wwPTKsWsG0q206kVe63iuWP/wEkEE4SIV0QngnKppDf54QezUKmar7UQby6HzufT5yVIOvj7QEqZnOavrKW0chCeCwP0khda/j9k00ct6NMEDxQFW+g==" + } + }, "ContainerInfo": { "description": "Information about container.", "type": "object", @@ -1649,7 +1704,7 @@ func init() { ], "responses": { "200": { - "description": "Base64 encoded stable binary marshaled bearer token.", + "description": "Base64 encoded stable binary marshaled bearer token bodies.", "schema": { "type": "array", "items": { @@ -1688,6 +1743,57 @@ func init() { } } }, + "/auth/bearer": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Form binary bearer token", + "operationId": "formBinaryBearer", + "parameters": [ + { + "type": "string", + "description": "Base64 encoded signature for bearer token.", + "name": "X-Bearer-Signature", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Hex encoded the public part of the key that signed the bearer token.", + "name": "X-Bearer-Signature-Key", + "in": "header", + "required": true + }, + { + "type": "boolean", + "default": false, + "description": "Use wallet connect signature scheme or native NeoFS signature.", + "name": "walletConnect", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Base64 encoded stable binary marshaled bearer token.", + "schema": { + "$ref": "#/definitions/BinaryBearer" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, "/containers": { "get": { "security": [], @@ -2491,6 +2597,22 @@ func init() { } ] }, + "BinaryBearer": { + "description": "Bearer token for object operations that is represented in binary form.", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "description": "Base64 encoded bearer token.", + "type": "string" + } + }, + "example": { + "token": "ChIKDAoAGggIARABIgIIAxoCCGQSZgohA+J5jFWFMiOpyvMZBu9wwPTKsWsG0q206kVe63iuWP/wEkEE4SIV0QngnKppDf54QezUKmar7UQby6HzufT5yVIOvj7QEqZnOavrKW0chCeCwP0khda/j9k00ct6NMEDxQFW+g==" + } + }, "ContainerInfo": { "description": "Information about container.", "type": "object", diff --git a/gen/restapi/operations/auth_responses.go b/gen/restapi/operations/auth_responses.go index 5502b2e..0957e66 100644 --- a/gen/restapi/operations/auth_responses.go +++ b/gen/restapi/operations/auth_responses.go @@ -16,7 +16,7 @@ import ( // AuthOKCode is the HTTP code returned for type AuthOK const AuthOKCode int = 200 -/*AuthOK Base64 encoded stable binary marshaled bearer token. +/*AuthOK Base64 encoded stable binary marshaled bearer token bodies. swagger:response authOK */ diff --git a/gen/restapi/operations/form_binary_bearer.go b/gen/restapi/operations/form_binary_bearer.go new file mode 100644 index 0000000..d163a30 --- /dev/null +++ b/gen/restapi/operations/form_binary_bearer.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/nspcc-dev/neofs-rest-gw/gen/models" +) + +// FormBinaryBearerHandlerFunc turns a function with the right signature into a form binary bearer handler +type FormBinaryBearerHandlerFunc func(FormBinaryBearerParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn FormBinaryBearerHandlerFunc) Handle(params FormBinaryBearerParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// FormBinaryBearerHandler interface for that can handle valid form binary bearer params +type FormBinaryBearerHandler interface { + Handle(FormBinaryBearerParams, *models.Principal) middleware.Responder +} + +// NewFormBinaryBearer creates a new http.Handler for the form binary bearer operation +func NewFormBinaryBearer(ctx *middleware.Context, handler FormBinaryBearerHandler) *FormBinaryBearer { + return &FormBinaryBearer{Context: ctx, Handler: handler} +} + +/* FormBinaryBearer swagger:route GET /auth/bearer formBinaryBearer + +Form binary bearer token + +*/ +type FormBinaryBearer struct { + Context *middleware.Context + Handler FormBinaryBearerHandler +} + +func (o *FormBinaryBearer) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewFormBinaryBearerParams() + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + *r = *aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/gen/restapi/operations/form_binary_bearer_parameters.go b/gen/restapi/operations/form_binary_bearer_parameters.go new file mode 100644 index 0000000..8df3416 --- /dev/null +++ b/gen/restapi/operations/form_binary_bearer_parameters.go @@ -0,0 +1,151 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NewFormBinaryBearerParams creates a new FormBinaryBearerParams object +// with the default values initialized. +func NewFormBinaryBearerParams() FormBinaryBearerParams { + + var ( + // initialize parameters with default values + + walletConnectDefault = bool(false) + ) + + return FormBinaryBearerParams{ + WalletConnect: &walletConnectDefault, + } +} + +// FormBinaryBearerParams contains all the bound params for the form binary bearer operation +// typically these are obtained from a http.Request +// +// swagger:parameters formBinaryBearer +type FormBinaryBearerParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Base64 encoded signature for bearer token. + Required: true + In: header + */ + XBearerSignature string + /*Hex encoded the public part of the key that signed the bearer token. + Required: true + In: header + */ + XBearerSignatureKey string + /*Use wallet connect signature scheme or native NeoFS signature. + In: query + Default: false + */ + WalletConnect *bool +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewFormBinaryBearerParams() beforehand. +func (o *FormBinaryBearerParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + if err := o.bindXBearerSignature(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature")], true, route.Formats); err != nil { + res = append(res, err) + } + + if err := o.bindXBearerSignatureKey(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature-Key")], true, route.Formats); err != nil { + res = append(res, err) + } + + qWalletConnect, qhkWalletConnect, _ := qs.GetOK("walletConnect") + if err := o.bindWalletConnect(qWalletConnect, qhkWalletConnect, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindXBearerSignature binds and validates parameter XBearerSignature from header. +func (o *FormBinaryBearerParams) bindXBearerSignature(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("X-Bearer-Signature", "header", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + + if err := validate.RequiredString("X-Bearer-Signature", "header", raw); err != nil { + return err + } + o.XBearerSignature = raw + + return nil +} + +// bindXBearerSignatureKey binds and validates parameter XBearerSignatureKey from header. +func (o *FormBinaryBearerParams) bindXBearerSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("X-Bearer-Signature-Key", "header", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + + if err := validate.RequiredString("X-Bearer-Signature-Key", "header", raw); err != nil { + return err + } + o.XBearerSignatureKey = raw + + return nil +} + +// bindWalletConnect binds and validates parameter WalletConnect from query. +func (o *FormBinaryBearerParams) bindWalletConnect(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewFormBinaryBearerParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("walletConnect", "query", "bool", raw) + } + o.WalletConnect = &value + + return nil +} diff --git a/gen/restapi/operations/form_binary_bearer_responses.go b/gen/restapi/operations/form_binary_bearer_responses.go new file mode 100644 index 0000000..eee7b3d --- /dev/null +++ b/gen/restapi/operations/form_binary_bearer_responses.go @@ -0,0 +1,124 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/nspcc-dev/neofs-rest-gw/gen/models" +) + +// FormBinaryBearerOKCode is the HTTP code returned for type FormBinaryBearerOK +const FormBinaryBearerOKCode int = 200 + +/*FormBinaryBearerOK Base64 encoded stable binary marshaled bearer token. + +swagger:response formBinaryBearerOK +*/ +type FormBinaryBearerOK struct { + /* + + */ + AccessControlAllowOrigin string `json:"Access-Control-Allow-Origin"` + + /* + In: Body + */ + Payload *models.BinaryBearer `json:"body,omitempty"` +} + +// NewFormBinaryBearerOK creates FormBinaryBearerOK with default headers values +func NewFormBinaryBearerOK() *FormBinaryBearerOK { + + return &FormBinaryBearerOK{} +} + +// WithAccessControlAllowOrigin adds the accessControlAllowOrigin to the form binary bearer o k response +func (o *FormBinaryBearerOK) WithAccessControlAllowOrigin(accessControlAllowOrigin string) *FormBinaryBearerOK { + o.AccessControlAllowOrigin = accessControlAllowOrigin + return o +} + +// SetAccessControlAllowOrigin sets the accessControlAllowOrigin to the form binary bearer o k response +func (o *FormBinaryBearerOK) SetAccessControlAllowOrigin(accessControlAllowOrigin string) { + o.AccessControlAllowOrigin = accessControlAllowOrigin +} + +// WithPayload adds the payload to the form binary bearer o k response +func (o *FormBinaryBearerOK) WithPayload(payload *models.BinaryBearer) *FormBinaryBearerOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the form binary bearer o k response +func (o *FormBinaryBearerOK) SetPayload(payload *models.BinaryBearer) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *FormBinaryBearerOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + // response header Access-Control-Allow-Origin + + accessControlAllowOrigin := o.AccessControlAllowOrigin + if accessControlAllowOrigin != "" { + rw.Header().Set("Access-Control-Allow-Origin", accessControlAllowOrigin) + } + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// FormBinaryBearerBadRequestCode is the HTTP code returned for type FormBinaryBearerBadRequest +const FormBinaryBearerBadRequestCode int = 400 + +/*FormBinaryBearerBadRequest Bad request + +swagger:response formBinaryBearerBadRequest +*/ +type FormBinaryBearerBadRequest struct { + + /* + In: Body + */ + Payload *models.ErrorResponse `json:"body,omitempty"` +} + +// NewFormBinaryBearerBadRequest creates FormBinaryBearerBadRequest with default headers values +func NewFormBinaryBearerBadRequest() *FormBinaryBearerBadRequest { + + return &FormBinaryBearerBadRequest{} +} + +// WithPayload adds the payload to the form binary bearer bad request response +func (o *FormBinaryBearerBadRequest) WithPayload(payload *models.ErrorResponse) *FormBinaryBearerBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the form binary bearer bad request response +func (o *FormBinaryBearerBadRequest) SetPayload(payload *models.ErrorResponse) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *FormBinaryBearerBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/gen/restapi/operations/neofs_rest_gw_api.go b/gen/restapi/operations/neofs_rest_gw_api.go index 328dbff..26253d6 100644 --- a/gen/restapi/operations/neofs_rest_gw_api.go +++ b/gen/restapi/operations/neofs_rest_gw_api.go @@ -53,6 +53,9 @@ func NewNeofsRestGwAPI(spec *loads.Document) *NeofsRestGwAPI { DeleteObjectHandler: DeleteObjectHandlerFunc(func(params DeleteObjectParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation DeleteObject has not yet been implemented") }), + FormBinaryBearerHandler: FormBinaryBearerHandlerFunc(func(params FormBinaryBearerParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation FormBinaryBearer has not yet been implemented") + }), GetBalanceHandler: GetBalanceHandlerFunc(func(params GetBalanceParams) middleware.Responder { return middleware.NotImplemented("operation GetBalance has not yet been implemented") }), @@ -157,6 +160,8 @@ type NeofsRestGwAPI struct { DeleteContainerHandler DeleteContainerHandler // DeleteObjectHandler sets the operation handler for the delete object operation DeleteObjectHandler DeleteObjectHandler + // FormBinaryBearerHandler sets the operation handler for the form binary bearer operation + FormBinaryBearerHandler FormBinaryBearerHandler // GetBalanceHandler sets the operation handler for the get balance operation GetBalanceHandler GetBalanceHandler // GetContainerHandler sets the operation handler for the get container operation @@ -279,6 +284,9 @@ func (o *NeofsRestGwAPI) Validate() error { if o.DeleteObjectHandler == nil { unregistered = append(unregistered, "DeleteObjectHandler") } + if o.FormBinaryBearerHandler == nil { + unregistered = append(unregistered, "FormBinaryBearerHandler") + } if o.GetBalanceHandler == nil { unregistered = append(unregistered, "GetBalanceHandler") } @@ -441,6 +449,10 @@ func (o *NeofsRestGwAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/auth/bearer"] = NewFormBinaryBearer(o.context, o.FormBinaryBearerHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/accounting/balance/{address}"] = NewGetBalance(o.context, o.GetBalanceHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/handlers/api.go b/handlers/api.go index 34214a6..eb16820 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -98,6 +98,8 @@ func (a *API) Configure(api *operations.NeofsRestGwAPI) http.Handler { api.OptionsAuthHandler = operations.OptionsAuthHandlerFunc(a.OptionsAuth) api.AuthHandler = operations.AuthHandlerFunc(a.PostAuth) + api.FormBinaryBearerHandler = operations.FormBinaryBearerHandlerFunc(a.FormBinaryBearer) + api.GetBalanceHandler = operations.GetBalanceHandlerFunc(a.Balance) api.OptionsObjectsPutHandler = operations.OptionsObjectsPutHandlerFunc(a.OptionsObjectsPut) diff --git a/handlers/auth.go b/handlers/auth.go index 33e47aa..8d5f936 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -102,6 +102,21 @@ func (a *API) PostAuth(params operations.AuthParams) middleware.Responder { WithAccessControlAllowOrigin("*") } +// FormBinaryBearer handler that forms binary bearer token using headers with body and signature. +func (a *API) FormBinaryBearer(params operations.FormBinaryBearerParams, principal *models.Principal) middleware.Responder { + btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect) + if err != nil { + resp := a.logAndGetErrorResponse("invalid bearer token", err) + return operations.NewFormBinaryBearerBadRequest().WithPayload(resp) + } + + resp := &models.BinaryBearer{ + Token: util.NewString(base64.StdEncoding.EncodeToString(btoken.Marshal())), + } + + return operations.NewFormBinaryBearerOK().WithPayload(resp) +} + func prepareObjectToken(ctx context.Context, params objectTokenParams, pool *pool.Pool, owner user.ID) (*models.TokenResponse, error) { btoken, err := util.ToNativeObjectToken(params.Records) if err != nil { diff --git a/handlers/auth_test.go b/handlers/auth_test.go index ca7d14f..5b2688c 100644 --- a/handlers/auth_test.go +++ b/handlers/auth_test.go @@ -7,6 +7,7 @@ import ( "crypto/sha512" "encoding/base64" "encoding/hex" + "fmt" "math" "testing" @@ -14,6 +15,8 @@ import ( "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-rest-gw/gen/models" "github.com/nspcc-dev/neofs-rest-gw/internal/util" + "github.com/nspcc-dev/neofs-sdk-go/bearer" + "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/stretchr/testify/require" ) @@ -70,3 +73,31 @@ func TestSign(t *testing.T) { _, err = prepareBearerToken(bt, false) require.NoError(t, err) } + +func TestName(t *testing.T) { + //bt := "Cq4BCokBCgASIgog1WV5EUB4P+vBDntpIRfpfqU9vFbfqYCLVQxeGiN4We4aVwgDEAEaTQgCEAEaBUVtYWlsIkA2NGJhM2FkNDdjYmVlNWY1Mjg4ZTZjOTZiNWUwNjg1NjZmMzM2MGMyOGIyODNiN2M2NmM0MmFiZTczNTFjNDQ5IgIIAxoICAMQAiICCAMSGwoZNaq5pfYuroaGE7h9o5iQsPR/1aRe5gmWrhoDCLcGEmYKIQMabG+73wLKNRdF+oa5ulqUUteFrE9/wrdUjKKkbE/PShJBBEGKRnVy4jfvBuNMspDwI6/fynIkqql4p+wKe/BgOKgDFkX83h6Q13J8eakV4RzZR+31JLgyM37GVp57ndZk9w4=" + // + //btRaw, err := base64.StdEncoding.DecodeString(bt) + //require.NoError(t, err) + + key, err := keys.NewPrivateKey() + require.NoError(t, err) + + var btoken bearer.Token + + var table eacl.Table + var r eacl.Record + r.SetAction(eacl.ActionAllow) + r.SetOperation(eacl.OperationGet) + eacl.AddFormedTarget(&r, eacl.RoleOthers) + + table.AddRecord(&r) + + btoken.SetExp(100) + btoken.SetEACLTable(table) + err = btoken.Sign(key.PrivateKey) + require.NoError(t, err) + + bytes := btoken.Marshal() + fmt.Println(base64.StdEncoding.EncodeToString(bytes)) +} diff --git a/spec/rest.yaml b/spec/rest.yaml index 51b541d..8418ddd 100644 --- a/spec/rest.yaml +++ b/spec/rest.yaml @@ -93,7 +93,7 @@ paths: - application/json responses: 200: - description: Base64 encoded stable binary marshaled bearer token. + description: Base64 encoded stable binary marshaled bearer token bodies. headers: Access-Control-Allow-Origin: type: string @@ -106,6 +106,29 @@ paths: schema: $ref: '#/definitions/ErrorResponse' + /auth/bearer: + get: + operationId: formBinaryBearer + summary: Form binary bearer token + parameters: + - $ref: '#/parameters/signatureParam' + - $ref: '#/parameters/signatureKeyParam' + - $ref: '#/parameters/signatureScheme' + produces: + - application/json + responses: + 200: + description: Base64 encoded stable binary marshaled bearer token. + headers: + Access-Control-Allow-Origin: + type: string + schema: + $ref: '#/definitions/BinaryBearer' + 400: + description: Bad request + schema: + $ref: '#/definitions/ErrorResponse' + /accounting/balance/{address}: get: operationId: getBalance @@ -182,7 +205,7 @@ paths: - $ref: '#/parameters/containerId' options: operationId: optionsObjectsSearch - security: [] + security: [ ] responses: 200: description: Base64 encoded stable binary marshaled bearer token. @@ -503,6 +526,17 @@ paths: $ref: '#/definitions/ErrorResponse' definitions: + BinaryBearer: + description: Bearer token for object operations that is represented in binary form. + type: object + properties: + token: + description: Base64 encoded bearer token. + type: string + required: + - token + example: + token: ChIKDAoAGggIARABIgIIAxoCCGQSZgohA+J5jFWFMiOpyvMZBu9wwPTKsWsG0q206kVe63iuWP/wEkEE4SIV0QngnKppDf54QezUKmar7UQby6HzufT5yVIOvj7QEqZnOavrKW0chCeCwP0khda/j9k00ct6NMEDxQFW+g== Bearer: description: Bearer token that is expected to be formed. type: object